feat(packaging): nix web console + plugin/script runner everywhere; fix tray zbus/tokio crash
ci / web (push) Successful in 58s
apple / swift (push) Successful in 1m24s
ci / docs-site (push) Successful in 59s
docker / build-push (--build-arg FEDORA_VERSION=44, ci, ci/fedora-rpm.Dockerfile, punktfunk-fedora44-rpm) (push) Successful in 17s
decky / build-publish (push) Successful in 25s
windows-host / package (push) Successful in 19m46s
docker / build-push (., web/Dockerfile, punktfunk-web) (push) Successful in 17s
docker / build-push (ci, ci/fedora-rpm.Dockerfile, punktfunk-fedora-rpm) (push) Successful in 17s
docker / build-push (ci, ci/rust-ci.Dockerfile, punktfunk-rust-ci) (push) Successful in 18s
docker / build-push (docs-site, docs-site/Dockerfile, punktfunk-docs) (push) Successful in 17s
android / android (push) Successful in 16m12s
arch / build-publish (push) Successful in 12m14s
ci / bench (push) Successful in 5m27s
apple / screenshots (push) Successful in 6m32s
ci / rust (push) Successful in 19m26s
deb / build-publish (push) Successful in 12m58s
docker / deploy-docs (push) Successful in 24s
rpm / build-publish (43, bazzite, punktfunk-fedora-rpm) (push) Successful in 24m51s
rpm / build-publish (44, fedora-44, punktfunk-fedora44-rpm) (push) Successful in 24m51s
ci / web (push) Successful in 58s
apple / swift (push) Successful in 1m24s
ci / docs-site (push) Successful in 59s
docker / build-push (--build-arg FEDORA_VERSION=44, ci, ci/fedora-rpm.Dockerfile, punktfunk-fedora44-rpm) (push) Successful in 17s
decky / build-publish (push) Successful in 25s
windows-host / package (push) Successful in 19m46s
docker / build-push (., web/Dockerfile, punktfunk-web) (push) Successful in 17s
docker / build-push (ci, ci/fedora-rpm.Dockerfile, punktfunk-fedora-rpm) (push) Successful in 17s
docker / build-push (ci, ci/rust-ci.Dockerfile, punktfunk-rust-ci) (push) Successful in 18s
docker / build-push (docs-site, docs-site/Dockerfile, punktfunk-docs) (push) Successful in 17s
android / android (push) Successful in 16m12s
arch / build-publish (push) Successful in 12m14s
ci / bench (push) Successful in 5m27s
apple / screenshots (push) Successful in 6m32s
ci / rust (push) Successful in 19m26s
deb / build-publish (push) Successful in 12m58s
docker / deploy-docs (push) Successful in 24s
rpm / build-publish (43, bazzite, punktfunk-fedora-rpm) (push) Successful in 24m51s
rpm / build-publish (44, fedora-44, punktfunk-fedora44-rpm) (push) Successful in 24m51s
Ship the management web console and the plugin/script runner across the packaging
matrix, and fix a Linux tray startup crash.
- nix: add punktfunk-web (Nitro SSR on bun) and punktfunk-scripting (Effect runner),
with services.punktfunk.{web,scripting} modules. Both bun node_modules come from
fixed-output derivations pinned to real deps hashes; patchShebangs the web
node_modules so orval/paraglide/vite run in the sandbox. Validated on x86_64-linux
(nix build .#punktfunk-{web,scripting,tray,host} all green).
- punktfunk-scripting now ships for deb/rpm/arch/nix/windows, mirroring the web-console
mechanics (vendored bun on deb/rpm/arch, pkgs.bun on nix; one self-contained
`bun build --target=bun` bundle). Opt-in: host Recommends it, but the systemd --user
unit ships un-started (Windows registers its scheduled task disabled).
- fix(tray): build punktfunk-tray in its OWN cargo invocation on nix/rpm/arch. Cargo
unifies features across one `cargo build`, so co-building the tray with the host pulls
the host's ashpd->zbus/tokio onto the tray's shared zbus and it panics at startup
("there is no reactor running, must be called from the context of a Tokio 1.x
runtime"). Isolated, the tray's zbus stays on async-io. The .deb already did this;
runtime-verified on nix.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
+54
-1
@@ -16,8 +16,10 @@ and the native Linux **client**, a **NixOS module** that wires up everything the
|
||||
| --- | --- |
|
||||
| `packages.x86_64-linux.punktfunk-host` | `punktfunk-host` + `punktfunk-tray` (built with `nvenc` + `vulkan-encode`, like CI) |
|
||||
| `packages.x86_64-linux.punktfunk-client` | `punktfunk-client` (GTK4 shell) + `punktfunk-session` (Vulkan streamer, without the Skia OSD — see caveats) |
|
||||
| `packages.x86_64-linux.punktfunk-web` | the management web console (bun-built Nitro SSR bundle; SPAKE2 pairing + host status) |
|
||||
| `packages.x86_64-linux.punktfunk-scripting` | the plugin/script runner (bun-bundled Effect SDK; supervises host automation) |
|
||||
| `packages.x86_64-linux.default` | = `punktfunk-host` |
|
||||
| `nixosModules.default` | `services.punktfunk.host` / `services.punktfunk.client` |
|
||||
| `nixosModules.default` | `services.punktfunk.host` / `.client` / `.web` / `.scripting` |
|
||||
| `devShells.x86_64-linux.default` | pinned Rust (from `rust-toolchain.toml`) + all build deps |
|
||||
| `apps` / `checks` / `formatter` | `nix run`, `nix flake check`, `nix fmt` |
|
||||
|
||||
@@ -105,6 +107,39 @@ systemctl --user enable --now punktfunk-host
|
||||
|
||||
`services.punktfunk.client`: `enable`, `openFirewall` (UDP 5353), `package`.
|
||||
|
||||
`services.punktfunk.web` (the management console — **on by default whenever the host is enabled**,
|
||||
mirroring the RPM's `Recommends: punktfunk-web`):
|
||||
|
||||
| Option | Default | Meaning |
|
||||
| --- | --- | --- |
|
||||
| `enable` | `host.enable` | Run the console as a `systemd --user` service on **TCP 47992 (HTTPS)**. Set `false` for a console-less host. |
|
||||
| `openFirewall` | `host.openFirewall` | Open TCP 47992 so other devices on the LAN can reach it. |
|
||||
| `autoStart` | `host.autoStart` | Add the console user service to `default.target` (appliance mode). |
|
||||
| `package` | flake's | Override the package. |
|
||||
|
||||
The console is **auto-wired** to the host on the same box: it reads the host's per-user
|
||||
`~/.config/punktfunk/{mgmt-token,cert.pem,key.pem}` (written by `serve`), serves HTTPS with the
|
||||
host's own identity cert, and proxies the loopback mgmt API with the bearer token injected
|
||||
server-side (never sent to the browser). A login password is generated on first start — read it
|
||||
with `journalctl --user -u punktfunk-web-init` (or `~/.config/punktfunk/web-password`). Then open
|
||||
`https://<host-ip>:47992` and trust the self-signed host cert once. Enable it (with the host) via
|
||||
`systemctl --user enable --now punktfunk-web`.
|
||||
|
||||
`services.punktfunk.scripting` (the plugin/script runner — installed with the host, but **opt-in to
|
||||
run**):
|
||||
|
||||
| Option | Default | Meaning |
|
||||
| --- | --- | --- |
|
||||
| `enable` | `host.enable` | Install the runner + define its `systemd --user` unit `punktfunk-scripting`. |
|
||||
| `autoStart` | `false` | Add the unit to `default.target`. Off even on an auto-start host — running operator scripts/plugins is a deliberate opt-in. |
|
||||
| `package` | flake's | Override the package. |
|
||||
|
||||
The runner discovers loose scripts under `~/.config/punktfunk/scripts` and installed
|
||||
`punktfunk-plugin-*` packages under `~/.config/punktfunk/plugins`, and supervises each as an Effect
|
||||
fiber (SIGTERM shuts the tree down structurally so plugin finalizers run). A plugin auto-wires to
|
||||
the host's mgmt token + identity cert. It's inert until you add automation, so the unit ships
|
||||
un-started; turn it on with `systemctl --user enable --now punktfunk-scripting`.
|
||||
|
||||
### What the host module configures for you
|
||||
|
||||
Everything the RPM's `%install` + `%post` do, declaratively:
|
||||
@@ -175,6 +210,24 @@ The shell exports `PF_FFVK_VULKAN_INCLUDE` (Vulkan headers for pf-ffvk bindgen)
|
||||
- **First build compiles from scratch** (no split dep cache — pyrowave-sys builds a CMake tree in
|
||||
its build.rs that a crane "dummy" source would drop) and has no public binary cache, so expect a
|
||||
long initial build. `nix develop` gives incremental rebuilds.
|
||||
- **The status tray is built in its own derivation, on purpose.** `punktfunk-tray` uses `ksni`'s
|
||||
`async-io` zbus executor with no tokio runtime (by design — see `crates/punktfunk-tray/Cargo.toml`).
|
||||
Cargo unifies features across everything in one `cargo build`, so co-building the tray with the
|
||||
host would pull the host's `ashpd → zbus/tokio` onto the tray's shared `zbus`, and the tray then
|
||||
panics at startup (`there is no reactor running, must be called from the context of a Tokio 1.x
|
||||
runtime`). Building it as a separate `-p punktfunk-tray` invocation keeps its `zbus` on async-io;
|
||||
the host package copies the resulting binary into its `$out`. (The deb/rpm/arch builds co-build the
|
||||
two in one `cargo build`, so they share this latent crash on Linux — a separate fix.)
|
||||
- **The bun packages (`punktfunk-web`, `punktfunk-scripting`) — their `bun install` deps hashes.**
|
||||
Both build their `node_modules` in a *fixed-output derivation* (`bun install` needs the network +
|
||||
the read-public `@unom` npm registry). Each `outputHash` (in `packaging/nix/packages.nix`) is
|
||||
pinned to a resolved dependency set and **must be refreshed when its lockfile changes** —
|
||||
`web/bun.lock` for the console, `sdk/bun.lock` for the runner: set that `outputHash = lib.fakeHash`,
|
||||
run `nix build .#punktfunk-web` (or `.#punktfunk-scripting`), and copy the `got: sha256-…` value
|
||||
Nix prints back into the field. Everything downstream is offline (the console's codegen + vite
|
||||
build; the runner's `bun build --target=bun` bundle), so only the deps FODs ever need network.
|
||||
Both launchers exec `pkgs.bun` from the store — unlike the deb/rpm, which vendor a bun binary
|
||||
because apt/dnf have none.
|
||||
- **Commit `flake.lock`:** it pins the input revisions (nixpkgs / crane / rust-overlay). It is
|
||||
generated on first eval and checked in.
|
||||
- **Session Skia OSD is off under Nix.** `punktfunk-session`'s default `ui` feature draws its
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
# NixOS integration for punktfunk — the declarative equivalent of everything the RPM/deb do in
|
||||
# their %install + %post (packaging/rpm/punktfunk.spec, packaging/debian/build-deb.sh):
|
||||
# the systemd *user* service, the uinput/uhid/vhci udev rules, the vhci-hcd autoload, the 32 MB
|
||||
# UDP socket-buffer sysctls, the firewall openers, and the `input`-group membership for virtual
|
||||
# gamepads.
|
||||
# UDP socket-buffer sysctls, the firewall openers, the `input`-group membership for virtual
|
||||
# gamepads, the management web console (`services.punktfunk.web`, on by default with the host — the
|
||||
# RPM/deb Recommends), and the opt-in plugin/script runner (`services.punktfunk.scripting`).
|
||||
#
|
||||
# Usage (flake):
|
||||
# { inputs.punktfunk.url = "git+https://git.unom.io/unom/punktfunk";
|
||||
@@ -149,6 +150,84 @@ in
|
||||
description = "Open UDP 5353 (mDNS) so the client can auto-discover hosts on the LAN.";
|
||||
};
|
||||
};
|
||||
|
||||
# The management web console (SPAKE2 PIN pairing + host status) — the browser UI every client
|
||||
# needs. Ships by DEFAULT alongside the host (mirrors the RPM's `Recommends: punktfunk-web` and
|
||||
# the .deb the host package pulls in), auto-wired to the host's mgmt token + identity cert.
|
||||
web = {
|
||||
enable = mkOption {
|
||||
type = types.bool;
|
||||
default = cfg.host.enable;
|
||||
defaultText = literalExpression "config.services.punktfunk.host.enable";
|
||||
description = ''
|
||||
Run the management web console as a `systemd --user` service on TCP 47992 (HTTPS). Enabled
|
||||
by default whenever the host is enabled — set to `false` for a console-less host. It
|
||||
auto-wires to `~/.config/punktfunk/{mgmt-token,cert.pem,key.pem}` (written by the host's
|
||||
`serve`) and generates a login password on first start.
|
||||
'';
|
||||
};
|
||||
|
||||
package = mkOption {
|
||||
type = types.package;
|
||||
default = self.packages.${system}.punktfunk-web;
|
||||
defaultText = literalExpression "punktfunk.packages.\${system}.punktfunk-web";
|
||||
description = "The punktfunk-web package (the bun-built Nitro SSR console bundle).";
|
||||
};
|
||||
|
||||
openFirewall = mkOption {
|
||||
type = types.bool;
|
||||
default = cfg.host.openFirewall;
|
||||
defaultText = literalExpression "config.services.punktfunk.host.openFirewall";
|
||||
description = "Open TCP 47992 so the console is reachable from other devices on the LAN.";
|
||||
};
|
||||
|
||||
autoStart = mkOption {
|
||||
type = types.bool;
|
||||
default = cfg.host.autoStart;
|
||||
defaultText = literalExpression "config.services.punktfunk.host.autoStart";
|
||||
description = ''
|
||||
Start the console automatically in every user's graphical session (adds it to the user
|
||||
`default.target`). Follows the host's `autoStart` by default — for a login-less appliance,
|
||||
enable lingering for the user as well.
|
||||
'';
|
||||
};
|
||||
};
|
||||
|
||||
# The plugin/script runner — host automation on bun. Ships with the host (the RPM/deb Recommends
|
||||
# it), but running it is OPT-IN: the `systemd --user` unit is defined yet NOT added to
|
||||
# `default.target`, because the runner is inert until you add scripts/plugins. Turn it on with
|
||||
# `systemctl --user enable --now punktfunk-scripting`.
|
||||
scripting = {
|
||||
enable = mkOption {
|
||||
type = types.bool;
|
||||
default = cfg.host.enable;
|
||||
defaultText = literalExpression "config.services.punktfunk.host.enable";
|
||||
description = ''
|
||||
Install the plugin/script runner and define its `systemd --user` unit
|
||||
(`punktfunk-scripting`). Enabled by default whenever the host is — but the unit is not
|
||||
auto-started (see `autoStart`), since the runner does nothing until you add scripts to
|
||||
`~/.config/punktfunk/scripts` or install `punktfunk-plugin-*` packages under
|
||||
`~/.config/punktfunk/plugins`. A plugin auto-wires to the host's mgmt token + identity cert.
|
||||
'';
|
||||
};
|
||||
|
||||
package = mkOption {
|
||||
type = types.package;
|
||||
default = self.packages.${system}.punktfunk-scripting;
|
||||
defaultText = literalExpression "punktfunk.packages.\${system}.punktfunk-scripting";
|
||||
description = "The punktfunk-scripting package (the bun-bundled Effect SDK runner).";
|
||||
};
|
||||
|
||||
autoStart = mkOption {
|
||||
type = types.bool;
|
||||
default = false;
|
||||
description = ''
|
||||
Start the runner automatically in every user's graphical session (adds it to the user
|
||||
`default.target`). Off by default even when the host auto-starts — running arbitrary
|
||||
operator scripts/plugins is a deliberate opt-in; enable it once you have automation to run.
|
||||
'';
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
config = mkMerge [
|
||||
@@ -226,5 +305,91 @@ in
|
||||
allowedUDPPorts = [ 5353 ];
|
||||
};
|
||||
})
|
||||
|
||||
# --- web console ---------------------------------------------------------------------------
|
||||
# The declarative equivalent of the punktfunk-web .deb / RPM subpackage: the two systemd --user
|
||||
# units (the console + its first-run password generator) plus the firewall opener, all auto-wired
|
||||
# to the host's per-user mgmt token + identity cert (no env editing on a packaged install).
|
||||
(mkIf cfg.web.enable {
|
||||
environment.systemPackages = [ cfg.web.package ];
|
||||
|
||||
networking.firewall = mkIf cfg.web.openFirewall {
|
||||
allowedTCPPorts = [ 47992 ]; # console HTTPS (packaging/linux/punktfunk-web.xml)
|
||||
};
|
||||
|
||||
# First-run setup: generate the console login password once, in the user's config dir, and
|
||||
# surface it to the --user journal. Self-gates via ConditionPathExists (mirrors
|
||||
# scripts/punktfunk-web-init.service).
|
||||
systemd.user.services.punktfunk-web-init = {
|
||||
description = "punktfunk web console first-run setup (login password)";
|
||||
documentation = [ "https://git.unom.io/unom/punktfunk" ];
|
||||
unitConfig.ConditionPathExists = "!%h/.config/punktfunk/web-password";
|
||||
path = [ pkgs.coreutils ];
|
||||
serviceConfig = {
|
||||
Type = "oneshot";
|
||||
RemainAfterExit = true;
|
||||
ExecStart = "${cfg.web.package}/share/punktfunk-web/web-init.sh";
|
||||
};
|
||||
};
|
||||
|
||||
# The console itself: Nitro SSR on bun, HTTPS on 47992 with the host's identity cert, proxying
|
||||
# the host's loopback mgmt API with the bearer token injected server-side. mgmt-token is
|
||||
# REQUIRED (the host's `serve` writes it) — if absent the unit fails and Restart retries until
|
||||
# the host has created it; web-password is optional ('-'). Mirrors scripts/punktfunk-web.service.
|
||||
systemd.user.services.punktfunk-web = {
|
||||
description = "punktfunk management web console";
|
||||
documentation = [ "https://git.unom.io/unom/punktfunk" ];
|
||||
after = [ "punktfunk-web-init.service" "punktfunk-host.service" ];
|
||||
wants = [ "punktfunk-web-init.service" ];
|
||||
wantedBy = optional cfg.web.autoStart "default.target";
|
||||
environment = {
|
||||
PUNKTFUNK_MGMT_URL = "https://127.0.0.1:47990";
|
||||
PORT = "47992";
|
||||
HOST = "0.0.0.0";
|
||||
# Serve HTTPS with the host's own identity cert (the anchor native clients already pin) and
|
||||
# mark the session cookie Secure. The host's `serve` writes these PEMs.
|
||||
PUNKTFUNK_UI_TLS_CERT = "%h/.config/punktfunk/cert.pem";
|
||||
PUNKTFUNK_UI_TLS_KEY = "%h/.config/punktfunk/key.pem";
|
||||
PUNKTFUNK_UI_SECURE = "1";
|
||||
};
|
||||
serviceConfig = {
|
||||
Type = "simple";
|
||||
EnvironmentFile = [
|
||||
"%h/.config/punktfunk/mgmt-token"
|
||||
"-%h/.config/punktfunk/web-password"
|
||||
];
|
||||
ExecStart = "${cfg.web.package}/bin/punktfunk-web-server";
|
||||
Restart = "on-failure";
|
||||
RestartSec = 2;
|
||||
};
|
||||
};
|
||||
})
|
||||
|
||||
# --- plugin/script runner ------------------------------------------------------------------
|
||||
# Installs the runner + defines its opt-in `systemd --user` unit (mirrors the deb/rpm
|
||||
# punktfunk-scripting subpackage). NOT auto-started unless `scripting.autoStart` is set.
|
||||
(mkIf cfg.scripting.enable {
|
||||
environment.systemPackages = [ cfg.scripting.package ];
|
||||
|
||||
systemd.user.services.punktfunk-scripting = {
|
||||
description = "punktfunk plugin/script runner";
|
||||
documentation = [ "https://git.unom.io/unom/punktfunk" ];
|
||||
# Plugins talk to the host's loopback mgmt API; order after it (soft — the runner backs off
|
||||
# and retries per unit, so this is ordering only, not a hard requirement).
|
||||
after = [ "punktfunk-host.service" ];
|
||||
wantedBy = optional cfg.scripting.autoStart "default.target";
|
||||
serviceConfig = {
|
||||
Type = "simple";
|
||||
ExecStart = "${cfg.scripting.package}/bin/punktfunk-scripting";
|
||||
Restart = "on-failure";
|
||||
RestartSec = 2;
|
||||
# Deliver SIGTERM to the runner (it orchestrates the structural shutdown of its unit
|
||||
# fibers) and give it room to run their finalizers before the cgroup is reaped.
|
||||
KillMode = "mixed";
|
||||
KillSignal = "SIGTERM";
|
||||
TimeoutStopSec = 30;
|
||||
};
|
||||
};
|
||||
})
|
||||
];
|
||||
}
|
||||
|
||||
+221
-2
@@ -56,6 +56,12 @@
|
||||
gsettings-desktop-schemas,
|
||||
adwaita-icon-theme,
|
||||
vulkan-headers,
|
||||
# web console (punktfunk-web): a bun-built Nitro SSR bundle, run on bun.
|
||||
bun,
|
||||
nodejs,
|
||||
makeWrapper,
|
||||
cacert,
|
||||
stdenvNoCC,
|
||||
}:
|
||||
let
|
||||
gbm = if libgbm != null then libgbm else mesa;
|
||||
@@ -101,12 +107,34 @@ let
|
||||
platforms = [ "x86_64-linux" ]; # NVENC desktop host; matches the RPM's ExclusiveArch: x86_64
|
||||
maintainers = [ ];
|
||||
};
|
||||
|
||||
# The Linux status tray, built in ISOLATION from the host — this is load-bearing, not tidiness.
|
||||
# Cargo unifies features across every package in a single `cargo build`, so co-building the tray
|
||||
# with the host would pull the host's `ashpd → zbus/tokio` onto the tray's SHARED `zbus`. The tray
|
||||
# deliberately runs zbus on `ksni`'s `async-io` executor with the `blocking` API and no tokio
|
||||
# runtime (see crates/punktfunk-tray/Cargo.toml), so a tokio-flavoured zbus panics at startup:
|
||||
# "there is no reactor running, must be called from the context of a Tokio 1.x runtime". A separate
|
||||
# invocation keeps the tray's zbus on async-io. (The host package copies this binary into its $out.)
|
||||
punktfunk-tray = craneLib.buildPackage (commonArgs // {
|
||||
pname = "punktfunk-tray";
|
||||
cargoExtraArgs = "--locked -p punktfunk-tray";
|
||||
PUNKTFUNK_BUILD_VERSION = buildVersion;
|
||||
# Pure-Rust leaf: ksni/zbus talk to the dbus socket, ureq+rustls(ring) + punktfunk-core `tls` —
|
||||
# nothing to link (no buildInputs) and no GPU driver runpath (it never dlopens libcuda/EGL/vulkan).
|
||||
meta = meta // {
|
||||
description = "punktfunk host status tray (Linux StatusNotifierItem)";
|
||||
mainProgram = "punktfunk-tray";
|
||||
};
|
||||
});
|
||||
in
|
||||
{
|
||||
inherit punktfunk-tray;
|
||||
|
||||
punktfunk-host = craneLib.buildPackage (commonArgs // {
|
||||
pname = "punktfunk-host";
|
||||
# HOST ONLY — the tray is a separate derivation (see the note above; co-building crashes it).
|
||||
cargoExtraArgs =
|
||||
"--locked -p punktfunk-host -p punktfunk-tray "
|
||||
"--locked -p punktfunk-host "
|
||||
+ "--features punktfunk-host/nvenc,punktfunk-host/vulkan-encode";
|
||||
|
||||
PUNKTFUNK_BUILD_VERSION = buildVersion;
|
||||
@@ -126,6 +154,10 @@ in
|
||||
# the host's authorization .desktop must name the ACTUAL binary path (the store path here). The
|
||||
# tray/client .desktop Exec lines are rewritten for the same reason (menu launch / autostart).
|
||||
postInstall = ''
|
||||
# The status tray, built in its own derivation (see the punktfunk-tray note above) so its zbus
|
||||
# stays on async-io; ship it in the host's $out so the tray .desktop below resolves it.
|
||||
install -Dm0755 ${punktfunk-tray}/bin/punktfunk-tray "$out/bin/punktfunk-tray"
|
||||
|
||||
# udev: /dev/uinput + /dev/uhid (virtual gamepads) + the vhci sysfs perms for the virtual Deck.
|
||||
install -Dm0644 scripts/60-punktfunk.rules "$out/lib/udev/rules.d/60-punktfunk.rules"
|
||||
|
||||
@@ -161,7 +193,8 @@ in
|
||||
# empty at build time): append the driver runpath so the runtime dlopen of libcuda.so.1 /
|
||||
# libnvidia-encode.so.1 / libEGL.so.1 / the GPU's libvulkan ICD resolves from the running system.
|
||||
postFixup = ''
|
||||
addDriverRunpath "$out/bin/punktfunk-host" "$out/bin/punktfunk-tray"
|
||||
# Only the host dlopens the GPU stack; the tray (its own derivation, copied in above) does not.
|
||||
addDriverRunpath "$out/bin/punktfunk-host"
|
||||
'';
|
||||
|
||||
meta = meta // {
|
||||
@@ -232,4 +265,190 @@ in
|
||||
mainProgram = "punktfunk-client";
|
||||
};
|
||||
});
|
||||
|
||||
# --- management web console (punktfunk-web) ------------------------------------------------------
|
||||
# The browser console every client needs for SPAKE2 PIN pairing + host status: a TanStack Start /
|
||||
# React app that vite builds into a Nitro SSR bundle, run on `bun` (the Nitro `bun` preset + a
|
||||
# custom Bun.serve TLS entry — node can't run it; web/nitro-entry/bun-https.mjs). This mirrors the
|
||||
# Debian punktfunk-web .deb (packaging/debian/build-web-deb.sh) and the RPM's `--with web`
|
||||
# subpackage, which the host package Recommends so a default install pulls the console too.
|
||||
#
|
||||
# Unlike apt/dnf — which have no bun in their repos and so VENDOR a bun binary into the package —
|
||||
# Nix has `pkgs.bun`, so the launcher just execs it from the store (no vendored runtime). The
|
||||
# systemd `--user` units + firewall wiring live in the NixOS module, pointed at this store path.
|
||||
punktfunk-web =
|
||||
let
|
||||
# Offline node_modules for the console. `bun install` needs the network AND the @unom npm
|
||||
# registry (web/.npmrc → https://git.unom.io/api/packages/unom/npm/, read-public: the same
|
||||
# anonymous pull CI's rpm/deb builds do), so it lives in a fixed-output derivation — FODs get
|
||||
# network, and `outputHash` pins the result. `--ignore-scripts` skips the install-time
|
||||
# `prepare` codegen (it wants ../api/openapi.json, outside this web-only src scope); the build
|
||||
# derivation below runs codegen itself where the whole tree is present.
|
||||
#
|
||||
# ⚠ When web/bun.lock changes, this hash must be refreshed: set `outputHash = lib.fakeHash`,
|
||||
# rebuild, and copy the sha256 Nix reports back here (see packaging/nix/README.md).
|
||||
webDeps = stdenvNoCC.mkDerivation {
|
||||
pname = "punktfunk-web-deps";
|
||||
inherit version;
|
||||
src = src + "/web";
|
||||
nativeBuildInputs = [ bun cacert ];
|
||||
dontConfigure = true;
|
||||
buildPhase = ''
|
||||
runHook preBuild
|
||||
export HOME=$TMPDIR
|
||||
export BUN_INSTALL_CACHE_DIR=$TMPDIR/bun-cache
|
||||
export SSL_CERT_FILE=${cacert}/etc/ssl/certs/ca-bundle.crt
|
||||
export NODE_EXTRA_CA_CERTS=$SSL_CERT_FILE
|
||||
# copyfile backend ⇒ node_modules is fully materialised (no links into the ephemeral
|
||||
# cache), so the tree survives the copy into the content-addressed $out.
|
||||
bun install --frozen-lockfile --ignore-scripts --no-progress --backend=copyfile
|
||||
runHook postBuild
|
||||
'';
|
||||
installPhase = ''
|
||||
runHook preInstall
|
||||
mkdir -p $out
|
||||
cp -R node_modules $out/node_modules
|
||||
runHook postInstall
|
||||
'';
|
||||
dontFixup = true;
|
||||
outputHashMode = "recursive";
|
||||
outputHashAlgo = "sha256";
|
||||
outputHash = "sha256-OA4NjwapsCV/z+0rftDCMAQJGWw63Mi/GARetmuy0QU="; # web/bun.lock deps (refresh on lockfile change; see README).
|
||||
};
|
||||
in
|
||||
stdenvNoCC.mkDerivation {
|
||||
pname = "punktfunk-web";
|
||||
inherit src version;
|
||||
# nodejs: the JS build tools' `.bin` shims are `#!/usr/bin/env node`; patchShebangs (below)
|
||||
# repoints them at this node so they run in the sandbox. bun is still the RUNTIME (the launcher
|
||||
# execs it); node is build-time only, for orval/paraglide/vite.
|
||||
nativeBuildInputs = [ bun nodejs makeWrapper ];
|
||||
|
||||
# No cross-derivation dep cache: codegen + the vite build are fully offline (every input is in
|
||||
# the vendored node_modules, the checked-in api/openapi.json, and web/project.inlang).
|
||||
buildPhase = ''
|
||||
runHook preBuild
|
||||
export HOME=$TMPDIR
|
||||
cp -R ${webDeps}/node_modules web/node_modules
|
||||
chmod -R u+w web/node_modules
|
||||
# The JS CLIs (orval, paraglide-js, vite, …) ship a `#!/usr/bin/env node` shebang, and the
|
||||
# build sandbox has no /usr/bin/env — rewrite them to the store `node` before running any
|
||||
# script (else `bun run codegen` dies with "bad interpreter: /usr/bin/env"). Patch the WHOLE
|
||||
# node_modules, not just .bin: bun's .bin entries are symlinks (skipped by patchShebangs'
|
||||
# `-type f`); the real shebang lives in each package's `dist/bin/*.js` that they point to.
|
||||
patchShebangs web/node_modules
|
||||
cd web
|
||||
# `codegen` = orval (a typed React-Query client from ../api/openapi.json) + paraglide-js i18n
|
||||
# compile; both write into src/ and are prerequisites of the build (normally the install-time
|
||||
# `prepare` hook, which was skipped in the deps FOD).
|
||||
bun run codegen
|
||||
# `build` = vite build ⇒ the Nitro `bun`-preset SSR bundle in .output (our Bun.serve TLS entry).
|
||||
bun run build
|
||||
# Guard: assert we produced the bun bundle, not a node one (same check the deb/rpm builders do).
|
||||
grep -q 'Bun\.serve' .output/server/index.mjs \
|
||||
|| { echo "ERROR: web/.output is not a bun bundle (wrong nitro preset)" >&2; exit 1; }
|
||||
cd ..
|
||||
runHook postBuild
|
||||
'';
|
||||
|
||||
installPhase = ''
|
||||
runHook preInstall
|
||||
# The SSR bundle + its static assets, plus the first-run helper and env sample.
|
||||
mkdir -p $out/share/punktfunk-web/.output
|
||||
cp -R web/.output/server $out/share/punktfunk-web/.output/server
|
||||
cp -R web/.output/public $out/share/punktfunk-web/.output/public
|
||||
install -Dm0755 scripts/web-init.sh $out/share/punktfunk-web/web-init.sh
|
||||
install -Dm0644 web/web.env.example $out/share/punktfunk-web/web.env.example
|
||||
|
||||
# PATH-stable launcher: run the SSR bundle on bun from the store (mirrors the deb/rpm
|
||||
# /usr/bin/punktfunk-web-server, minus the vendored-bun indirection).
|
||||
makeWrapper ${bun}/bin/bun $out/bin/punktfunk-web-server \
|
||||
--add-flags "$out/share/punktfunk-web/.output/server/index.mjs"
|
||||
runHook postInstall
|
||||
'';
|
||||
|
||||
dontFixup = true;
|
||||
|
||||
meta = meta // {
|
||||
description = "punktfunk management web console (Nitro SSR on bun + React)";
|
||||
mainProgram = "punktfunk-web-server";
|
||||
};
|
||||
};
|
||||
|
||||
# --- plugin/script runner (punktfunk-scripting) --------------------------------------------------
|
||||
# The host's automation runner: the `@punktfunk/host` SDK's `punktfunk-scripting` CLI (built on
|
||||
# Effect), which discovers ~/.config/punktfunk/{scripts,plugins} and supervises each unit as an
|
||||
# Effect fiber. It runs on `bun` (it import()s the operator's `.ts` plugin files, which only bun
|
||||
# can do). Mirrors the Debian punktfunk-scripting .deb / the RPM's `--with scripting` subpackage,
|
||||
# which the host package Recommends — the NixOS module wires the opt-in systemd --user unit.
|
||||
#
|
||||
# Unlike the deb/rpm we don't `bun build` into a bundle + vendor bun; we still bundle (one
|
||||
# self-contained JS, effect inlined) but the launcher execs `pkgs.bun` from the store.
|
||||
punktfunk-scripting =
|
||||
let
|
||||
# Offline node_modules for the SDK build — same fixed-output pattern as punktfunk-web's webDeps
|
||||
# (`bun install` needs the network). ⚠ Refresh `outputHash` when sdk/bun.lock changes (set
|
||||
# lib.fakeHash, rebuild, copy the printed sha256 — see packaging/nix/README.md).
|
||||
sdkDeps = stdenvNoCC.mkDerivation {
|
||||
pname = "punktfunk-scripting-deps";
|
||||
inherit version;
|
||||
src = src + "/sdk";
|
||||
nativeBuildInputs = [ bun cacert ];
|
||||
dontConfigure = true;
|
||||
buildPhase = ''
|
||||
runHook preBuild
|
||||
export HOME=$TMPDIR
|
||||
export BUN_INSTALL_CACHE_DIR=$TMPDIR/bun-cache
|
||||
export SSL_CERT_FILE=${cacert}/etc/ssl/certs/ca-bundle.crt
|
||||
export NODE_EXTRA_CA_CERTS=$SSL_CERT_FILE
|
||||
bun install --frozen-lockfile --ignore-scripts --no-progress --backend=copyfile
|
||||
runHook postBuild
|
||||
'';
|
||||
installPhase = ''
|
||||
runHook preInstall
|
||||
mkdir -p $out
|
||||
cp -R node_modules $out/node_modules
|
||||
runHook postInstall
|
||||
'';
|
||||
dontFixup = true;
|
||||
outputHashMode = "recursive";
|
||||
outputHashAlgo = "sha256";
|
||||
outputHash = "sha256-+KCKCA0q0bwTxr7bsA3X4DbT/8nUjJA/JIoJU6BfiZw="; # sdk/bun.lock deps (refresh on lockfile change; see README).
|
||||
};
|
||||
in
|
||||
stdenvNoCC.mkDerivation {
|
||||
pname = "punktfunk-scripting";
|
||||
inherit src version;
|
||||
nativeBuildInputs = [ bun makeWrapper ];
|
||||
|
||||
# `bun build --target=bun` bundles the runner CLI to ONE self-contained JS: effect + the SDK
|
||||
# are inlined, and the runner's dynamic `import()` of the operator's plugin files is left as a
|
||||
# runtime import (bun keeps unresolvable dynamic specifiers external). Fully offline.
|
||||
buildPhase = ''
|
||||
runHook preBuild
|
||||
export HOME=$TMPDIR
|
||||
cp -R ${sdkDeps}/node_modules sdk/node_modules
|
||||
chmod -R u+w sdk/node_modules
|
||||
( cd sdk && bun build src/runner-cli.ts --target=bun --outfile=$TMPDIR/runner-cli.js )
|
||||
grep -q 'attempt=' $TMPDIR/runner-cli.js \
|
||||
|| { echo "ERROR: runner bundle missing the dynamic plugin import — wrong build" >&2; exit 1; }
|
||||
runHook postBuild
|
||||
'';
|
||||
|
||||
installPhase = ''
|
||||
runHook preInstall
|
||||
install -Dm0644 $TMPDIR/runner-cli.js $out/share/punktfunk-scripting/runner-cli.js
|
||||
# Launcher: run the bundle on bun from the store (mirrors the deb/rpm /usr/bin/punktfunk-scripting).
|
||||
makeWrapper ${bun}/bin/bun $out/bin/punktfunk-scripting \
|
||||
--add-flags "$out/share/punktfunk-scripting/runner-cli.js"
|
||||
runHook postInstall
|
||||
'';
|
||||
|
||||
dontFixup = true;
|
||||
|
||||
meta = meta // {
|
||||
description = "punktfunk plugin/script runner (Effect SDK on bun)";
|
||||
mainProgram = "punktfunk-scripting";
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user