From 2a135612abc66841e99671e373e85194e0afe333 Mon Sep 17 00:00:00 2001 From: Felix Tenley Date: Wed, 7 Oct 2020 14:37:57 +0200 Subject: [PATCH] fix(rpi4): set up deconz --- flake.nix | 7 +- pkgs/deconz/default.nix | 42 ++++++++++++ services/deconz.nix | 123 ++++++++++++++++++++++++++++++++++++ services/home-assistant.nix | 34 ++++++++++ 4 files changed, 205 insertions(+), 1 deletion(-) create mode 100644 pkgs/deconz/default.nix create mode 100644 services/deconz.nix diff --git a/flake.nix b/flake.nix index 7034e08..d757a56 100644 --- a/flake.nix +++ b/flake.nix @@ -29,7 +29,12 @@ nix.registry.nixpkgs.flake = nixpkgs; - nixpkgs.overlays = [ nur.overlay ]; + nixpkgs.overlays = [ + nur.overlay + (self: super: { + deconz = pkgs.qt5.callPackage ./pkgs/deconz { }; + }) + ]; imports = [ hardwareConfig home-manager.nixosModules.home-manager config ]; diff --git a/pkgs/deconz/default.nix b/pkgs/deconz/default.nix new file mode 100644 index 0000000..252c300 --- /dev/null +++ b/pkgs/deconz/default.nix @@ -0,0 +1,42 @@ +{ stdenv, fetchurl, mkDerivation, dpkg, autoPatchelfHook +, qtserialport, qtwebsockets +, libredirect, makeWrapper, gzip, gnutar +}: + +mkDerivation rec { + name = "deconz-${version}"; + version = "2.05.82"; + + src = fetchurl { + url = "https://deconz.dresden-elektronik.de/raspbian/alpha/deconz_${version}-debian-stretch-beta_arm64.deb"; + sha256 = "cCH7XhRXCHKm5AVsM19TyHwAjhbTv4qyDx2GamuDWQw="; + }; + + nativeBuildInputs = [ dpkg autoPatchelfHook makeWrapper ]; + + buildInputs = [ qtserialport qtwebsockets ]; + + unpackPhase = "dpkg-deb -x $src ."; + + installPhase = '' + mkdir -p "$out" + cp -r usr/* . + cp -r share/deCONZ/plugins/* lib/ + cp -r . $out + + wrapProgram "$out/bin/deCONZ" \ + --set LD_PRELOAD "${libredirect}/lib/libredirect.so" \ + --set NIX_REDIRECTS "/usr/share=$out/share:/usr/bin=$out/bin" \ + --prefix PATH : "${stdenv.lib.makeBinPath [ gzip gnutar ]}" + ''; + + meta = with stdenv.lib; { + description = "Manage ZigBee network with ConBee, ConBee II or RaspBee hardware"; + # 2019-08-19: The homepage links to old software that doesn't even work -- + # it fails to detect ConBee2. + homepage = "https://www.dresden-elektronik.de/funktechnik/products/software/pc-software/deconz/?L=1"; + license = licenses.unfree; + platforms = with platforms; linux; + maintainers = with maintainers; [ felschr ]; + }; +} diff --git a/services/deconz.nix b/services/deconz.nix new file mode 100644 index 0000000..8047ac9 --- /dev/null +++ b/services/deconz.nix @@ -0,0 +1,123 @@ +# FIXME: These two auth issues: +# https://github.com/dresden-elektronik/deconz-rest-plugin/issues/1788 ("Auth problems on non-80 http port") +# https://github.com/dresden-elektronik/deconz-rest-plugin/issues/1792 ("Trying to change password: "Service not available. Try again later.") + +{ config, lib, pkgs, ... }: + +with lib; + +let + cfg = config.local.services.deconz; + name = "deconz"; + stateDir = "/var/lib/${name}"; +in +{ + options.local.services.deconz = { + + enable = mkEnableOption "deCONZ, a ZigBee gateway"; + + package = mkOption { + type = types.package; + default = pkgs.deconz; + defaultText = "pkgs.deconz"; + description = "Which deCONZ package to use."; + }; + + device = mkOption { + type = types.str; + default = ""; + description = '' + Force deCONZ to use a specific USB device (e.g. /dev/ttyACM0). By + default it does a search. + ''; + }; + + httpPort = mkOption { + type = types.port; + default = 80; + description = "TCP port for the web server."; + }; + + wsPort = mkOption { + type = types.port; + default = 443; + description = "TCP port for the WebSocket."; + }; + + openFirewall = mkEnableOption "open up the service ports in the firewall"; + + allowRebootSystem = mkEnableOption "allow rebooting the system"; + + allowRestartService = mkEnableOption "allow killing/restarting processes"; + + allowSetSystemTime = mkEnableOption "allow setting the system time"; + + extraOpts = mkOption { + type = types.listOf types.str; + default = [ + "--auto-connect=1" + "--dbg-info=1" + ]; + description = '' + Extra command line options for deCONZ. + These options seem undocumented, but some examples can be found here: + https://github.com/marthoc/docker-deconz/blob/master/amd64/root/start.sh + ''; + }; + }; + + config = mkIf cfg.enable { + + networking.firewall.allowedTCPPorts = lib.mkIf cfg.openFirewall [ + cfg.httpPort + cfg.wsPort + ]; + + systemd.services.deconz = { + description = "deCONZ ZigBee gateway"; + wantedBy = [ "multi-user.target" ]; + + preStart = '' + # The service puts a nix store path reference in here, and that path can + # be garbage collected. Ensure the file gets "refreshed" on every start. + rm -f ${stateDir}/.local/share/dresden-elektronik/deCONZ/zcldb.txt + ''; + serviceConfig = { + ExecStart = + "${cfg.package}/bin/deCONZ" + + " -platform minimal" + + " --http-port=${toString cfg.httpPort}" + + " --ws-port=${toString cfg.wsPort}" + + (if cfg.device != "" then " --dev=${cfg.device}" else "") + + " " + (lib.concatStringsSep " " cfg.extraOpts); + Restart = "on-failure"; + AmbientCapabilities = + let + # ref. upstream deconz.service + caps = lib.optionals (cfg.httpPort < 1024 || cfg.wsPort < 1024) [ "CAP_NET_BIND_SERVICE" ] + ++ lib.optionals (cfg.allowRebootSystem) [ "CAP_SYS_BOOT" ] + ++ lib.optionals (cfg.allowRestartService) [ "CAP_KILL" ] + ++ lib.optionals (cfg.allowSetSystemTime) [ "CAP_SYS_TIME" ]; + in + lib.concatStringsSep " " caps; + UMask = "0027"; + User = name; + StateDirectory = name; + WorkingDirectory = stateDir; + + ProtectSystem = "strict"; + ProtectHome = true; + ReadWritePaths = "/tmp"; + }; + }; + + users.users.deconz = { + group = name; + isSystemUser = true; + home = stateDir; + extraGroups = [ "dialout" ]; # for access to /dev/ttyACM0 (ConBee) + }; + + users.groups.deconz = {}; + }; +} diff --git a/services/home-assistant.nix b/services/home-assistant.nix index 04359f9..3a68bb9 100644 --- a/services/home-assistant.nix +++ b/services/home-assistant.nix @@ -1,9 +1,38 @@ { config, pkgs, ... }: with pkgs; +let + pydeconz = python3.pkgs.buildPythonPackage rec { + pname = "pydeconz"; + version = "73"; + + src = python3.pkgs.fetchPypi { + inherit pname version; + sha256 = "Lm7J0p2dp2gyesDpgN0WGpxPewC1z/IUy0CDEqofQGA="; + }; + + propagateBuildInputs = with python3Packages; [ setuptools ]; + buildInputs = with python3Packages; [ aiohttp ]; + doCheck = false; + }; +in { + imports = [ ./deconz.nix ]; + + environment.systemPackages = with pkgs; [ deconz ]; + + local.services.deconz = { + enable = true; + httpPort = 8080; + wsPort = 1443; + openFirewall = true; + }; + services.home-assistant = { enable = true; + package = home-assistant.override { + extraPackages = ps: with ps; [ pydeconz ]; + }; openFirewall = true; config = { homeassistant = { @@ -28,6 +57,11 @@ with pkgs; mqtt_topic = "owntracks/#"; secret = "!secret owntracks_secret"; }; + deconz = { + host = "localhost"; + port = 8080; + api_key = "!secret deconz_apikey"; + }; }; # configWritable = true; # doesn't work atm };