Add Nix package, NixOS module, and tests for bambuddy
- packages.bambuddy: FastAPI backend + prebuilt React frontend assembled into the app-root layout upstream expects, behind a single `bambuddy` launcher (uvicorn --loop asyncio, ffmpeg on PATH, XDG state defaults). Pins upstream maziggy/bambuddy v1.2.5.7. - nixosModules.default: services.bambuddy with host/port/dataDir, openFirewall, environment + environmentFile, extraPackages, DynamicUser + StateDirectory, systemd hardening, and CAP_NET_BIND_SERVICE for the virtual-printer FTPS/RTSP ports. - checks.bambuddy-import: backend import + admin CLI smoke test. - checks.bambuddy-service: two-node NixOS VM test covering the health endpoint, UI/static assets/manifest, SQLite + logs in the StateDirectory, journal startup check, cross-node reachability with openFirewall, custom host/port/env node, and loopback-only binding. - overlay, run app, devShell, README, .gitignore.
This commit is contained in:
commit
5fb8e8d724
9 changed files with 761 additions and 0 deletions
174
nixos/module.nix
Normal file
174
nixos/module.nix
Normal file
|
|
@ -0,0 +1,174 @@
|
|||
{
|
||||
config,
|
||||
lib,
|
||||
pkgs,
|
||||
...
|
||||
}:
|
||||
let
|
||||
cfg = config.services.bambuddy;
|
||||
|
||||
# The default dataDir maps to a systemd StateDirectory, which gets proper
|
||||
# ownership/permission handling for the DynamicUser. Custom paths must be
|
||||
# writable already; we grant access via ReadWritePaths.
|
||||
stateDirName = "bambuddy";
|
||||
defaultDataDir = "/var/lib/${stateDirName}";
|
||||
dataDirManaged = cfg.dataDir == defaultDataDir;
|
||||
in
|
||||
{
|
||||
options.services.bambuddy = {
|
||||
enable = lib.mkEnableOption "Bambuddy, a self-hosted command center for Bambu Lab 3D printers";
|
||||
|
||||
package = lib.mkOption {
|
||||
type = lib.types.package;
|
||||
description = ''
|
||||
The bambuddy package to run. Defaults to the package built by this
|
||||
flake.
|
||||
'';
|
||||
};
|
||||
|
||||
host = lib.mkOption {
|
||||
type = lib.types.str;
|
||||
default = "0.0.0.0";
|
||||
description = ''
|
||||
Address the web UI and API bind to. Use `127.0.0.1` when Bambuddy
|
||||
should only be reachable via a reverse proxy on the same host.
|
||||
'';
|
||||
};
|
||||
|
||||
port = lib.mkOption {
|
||||
type = lib.types.port;
|
||||
default = 8000;
|
||||
description = "Port the web UI and API listen on.";
|
||||
};
|
||||
|
||||
dataDir = lib.mkOption {
|
||||
type = lib.types.str;
|
||||
default = defaultDataDir;
|
||||
description = ''
|
||||
Persistent state directory: SQLite database, print archive, plate
|
||||
calibration data and logs live here. The default path under
|
||||
`/var/lib` is created automatically. Custom paths are used as-is and
|
||||
must be writable by the service user (a DynamicUser).
|
||||
'';
|
||||
};
|
||||
|
||||
openFirewall = lib.mkOption {
|
||||
type = lib.types.bool;
|
||||
default = false;
|
||||
description = ''
|
||||
Open the web port in the firewall so the dashboard is reachable from
|
||||
the LAN. Bambuddy itself always initiates printer connections
|
||||
(MQTT/FTP/RTSP), so no other inbound ports are required for printer
|
||||
communication.
|
||||
'';
|
||||
};
|
||||
|
||||
environment = lib.mkOption {
|
||||
type = lib.types.attrsOf lib.types.str;
|
||||
default = { };
|
||||
example = lib.literalExpression ''
|
||||
{
|
||||
LOG_LEVEL = "DEBUG";
|
||||
VIRTUAL_PRINTER_PASV_ADDRESS = "192.168.1.10";
|
||||
BAMBUDDY_EXTERNAL_ROOTS = "/mnt/nas/3d-prints";
|
||||
}
|
||||
'';
|
||||
description = ''
|
||||
Extra environment variables for Bambuddy. See upstream
|
||||
`.env.example` for the full catalogue (e.g. `DATABASE_URL` for
|
||||
external PostgreSQL, `MFA_ENCRYPTION_KEY`, `SLICER_API_URL`,
|
||||
`BAMBUDDY_EXTERNAL_ROOTS`, `USE_SYSTEM_TRUST_STORE`).
|
||||
'';
|
||||
};
|
||||
|
||||
environmentFile = lib.mkOption {
|
||||
type = lib.types.nullOr lib.types.path;
|
||||
default = null;
|
||||
example = "/var/lib/secrets/bambuddy.env";
|
||||
description = ''
|
||||
Environment file (KEY=value lines) loaded into the service. Useful
|
||||
for secrets that should not live in the world-readable Nix store,
|
||||
such as `MFA_ENCRYPTION_KEY` or OIDC credentials.
|
||||
'';
|
||||
};
|
||||
|
||||
extraPackages = lib.mkOption {
|
||||
type = lib.types.listOf lib.types.package;
|
||||
default = [ ];
|
||||
example = lib.literalExpression "[ pkgs.tailscale ]";
|
||||
description = ''
|
||||
Additional packages made available on the service PATH (ffmpeg is
|
||||
always included). For example, add `tailscale` when using the
|
||||
virtual printer's Tailscale certificate integration.
|
||||
'';
|
||||
};
|
||||
};
|
||||
|
||||
config = lib.mkIf cfg.enable {
|
||||
networking.firewall.allowedTCPPorts = lib.mkIf cfg.openFirewall [ cfg.port ];
|
||||
|
||||
systemd.services.bambuddy = {
|
||||
description = "Bambuddy — self-hosted command center for Bambu Lab 3D printers";
|
||||
documentation = [ "https://wiki.bambuddy.cool" ];
|
||||
wantedBy = [ "multi-user.target" ];
|
||||
after = [ "network-online.target" ];
|
||||
wants = [ "network-online.target" ];
|
||||
|
||||
environment = {
|
||||
HOST = cfg.host;
|
||||
PORT = toString cfg.port;
|
||||
DATA_DIR = cfg.dataDir;
|
||||
LOG_DIR = "${cfg.dataDir}/logs";
|
||||
HOME = cfg.dataDir;
|
||||
MPLCONFIGDIR = "${cfg.dataDir}/.cache/matplotlib";
|
||||
UVICORN_TIMEOUT_GRACEFUL_SHUTDOWN = "10";
|
||||
} // cfg.environment;
|
||||
|
||||
path = [ pkgs.ffmpeg ] ++ cfg.extraPackages;
|
||||
|
||||
serviceConfig = {
|
||||
ExecStart = lib.getExe cfg.package;
|
||||
EnvironmentFile = lib.mkIf (cfg.environmentFile != null) [ cfg.environmentFile ];
|
||||
|
||||
DynamicUser = true;
|
||||
StateDirectory = lib.mkIf dataDirManaged stateDirName;
|
||||
ReadWritePaths = lib.mkIf (!dataDirManaged) [ cfg.dataDir ];
|
||||
|
||||
# The virtual printer FTP (990) and RTSP camera proxy (322) listeners
|
||||
# bind privileged ports as the unprivileged service user — the same
|
||||
# NET_BIND_SERVICE grant the upstream docker-compose.yml uses.
|
||||
CapabilityBoundingSet = [ "CAP_NET_BIND_SERVICE" ];
|
||||
AmbientCapabilities = [ "CAP_NET_BIND_SERVICE" ];
|
||||
|
||||
Restart = "on-failure";
|
||||
RestartSec = 5;
|
||||
TimeoutStopSec = 30;
|
||||
|
||||
# Hardening. Bambuddy talks to printers over MQTT/FTP/RTSP and shells
|
||||
# out to ffmpeg, hence AF_INET/AF_UNIX and an exec-capable /tmp.
|
||||
NoNewPrivileges = true;
|
||||
PrivateTmp = true;
|
||||
ProtectSystem = "strict";
|
||||
ProtectHome = true;
|
||||
ProtectClock = true;
|
||||
ProtectHostname = true;
|
||||
ProtectKernelLogs = true;
|
||||
ProtectKernelModules = true;
|
||||
ProtectKernelTunables = true;
|
||||
ProtectControlGroups = true;
|
||||
ProtectProc = "invisible";
|
||||
RestrictAddressFamilies = [
|
||||
"AF_INET"
|
||||
"AF_INET6"
|
||||
"AF_UNIX"
|
||||
"AF_NETLINK"
|
||||
];
|
||||
RestrictNamespaces = true;
|
||||
RestrictRealtime = true;
|
||||
RestrictSUIDSGID = true;
|
||||
LockPersonality = true;
|
||||
SystemCallArchitectures = "native";
|
||||
};
|
||||
};
|
||||
};
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue