NixOS / nixpkgs

Nix Packages collection & NixOS
MIT License
18.32k stars 14.29k forks source link

ESPHome module: Failure to compile firmware because of DynamicUsers #339557

Open matt1432 opened 2 months ago

matt1432 commented 2 months ago

Describe the bug

After setting up ESPHome, plugging in the device to the server and configuring its yaml configuration, trying to install it causes a compilation failure with this error:

Multiple ways to build the same target were specified for: /var/lib/esphome/.esphome/build/m5stack-atom-echo-1/.pioenvs/m5stack-atom-echo-1/adc.o  (from ['/var/lib/private/esphome/.platformio/packages/framework-espidf/components/driver/adc.c'] and from ['/var/lib/private/esphome/.platformio/packages/framework-espidf/components/driver/esp32/adc.c'])
File "/var/lib/esphome/.platformio/platforms/espressif32/builder/frameworks/espidf.py", line 684, in compile_source_files

Steps To Reproduce

  1. Set up ESPHome with this config and a reverse proxy for https
    {
    services.esphome = {
    enable = true;
    address = "100.64.0.10";
    port = 6052;
    };
    }
  2. Get the yaml config for my device from here: https://github.com/esphome/wake-word-voice-assistants
  3. Connect the device to the server using a USB cable
  4. Click on install and wait for error to show up

Expected behavior

I expect the firmware to be compiled.

Additional context

Because of DynamicUsers, all of the esphome user's files are placed in /var/lib/private/esphome which gets symlinked to /var/lib/esphome (source). This apparently causes issues with ESPHome and platformio as seen here: https://github.com/platformio/platform-espressif32/issues/515.

I managed to work around this and get it to work by doing this:

{config, lib, ...}: {
  systemd.services.esphome = let
    inherit (lib) mkForce;

    cfg = config.services.esphome;
    stateDir = "/var/lib/private/esphome";
    esphomeParams =
      if cfg.enableUnixSocket
      then "--socket /run/esphome/esphome.sock"
      else "--address ${cfg.address} --port ${toString cfg.port}";
  in {
    environment.PLATFORMIO_CORE_DIR = mkForce "/var/lib/private/esphome/.platformio";

    serviceConfig = {
      ExecStart = mkForce "${cfg.package}/bin/esphome dashboard ${esphomeParams} ${stateDir}";
      WorkingDirectory = mkForce stateDir;
    };
  };
}

I don't know if this is the proper way to get this to work which is why I didn't do a PR.

Notify maintainers

@oddlama @megheaiulian @Stunkymonkey @rhoriguchi

Metadata

[user@system:~]$ nix-shell -p nix-info --run "nix-info -m"
 - system: `"x86_64-linux"`
 - host os: `Linux 6.6.48, NixOS, 24.11 (Vicuna), 24.11.20240831.12228ff`
 - multi-user?: `yes`
 - sandbox: `yes`
 - version: `nix-env (Nix) 2.24.4`
 - nixpkgs: `/nix/store/bd4fmzws6n5542khxbifbkr6nrygi232-source`

Add a :+1: reaction to issues you find important.

rhoriguchi commented 2 months ago

Could you attach the exact config file you are using.

Have you tried to compile the firmware manually?

nix-shell -p esphome --command 'esphome compile /var/lib/esphome/DEVICE-CONFIG.yaml'
matt1432 commented 2 months ago

@rhoriguchi

I'm not sure how I can compile the firmware manually, I keep getting Permission denied: ... even with sudo -u esphome.

However, I changed the ExecStart to the compile command and it only worked when PLATFORMIO_CORE_DIR pointed to /var/lib/private/esphome/.platformio.

All my nix code related to ESPHome is in my first message if that's what you asked.

This is my device config

packages:
  remote_package: github://esphome/firmware/voice-assistant/m5stack-atom-echo.adopted.yaml@main

api:
  encryption:
    key: "xxx"

ota:
  - platform: esphome
    password: "xxx"

wifi:
  ssid: !secret wifi_ssid
  password: !secret wifi_password

  manual_ip:
    static_ip: 192.168.0.92
    gateway: 192.168.0.1
    subnet: 255.255.255.0

  ap:
    ssid: "Esp1 Fallback Hotspot"
    password: "xxx"
rhoriguchi commented 2 months ago

Tested it with your config example and got the same issue. The option I suspect causing this issue here https://github.com/NixOS/nixpkgs/blob/574d1eac1c200690e27b8eb4e24887f8df7ac27c/nixos/modules/services/home-automation/esphome.nix#L118

From the man page:

ProtectSystem= Takes a boolean argument or the special values "full" or "strict". If true, mounts the /usr/ and the boot loader directories (/boot and /efi) read-only for processes invoked by this unit. If set to "full", the /etc/ directory is mounted read-only, too. If set to "strict" the entire file system hierarchy is mounted read-only, except for the API file system subtrees /dev/, /proc/ and /sys/ (protect these directories using PrivateDevices=, ProtectKernelTunables=, ProtectControlGroups=). This setting ensures that any modification of the vendor-supplied operating system (and optionally its configuration, and local mounts) is prohibited for the service. It is recommended to enable this setting for all long-running services, unless they are involved with system updates or need to modify the operating system in other ways. If this option is used, ReadWritePaths= may be used to exclude specific directories from being made read-only. Similar, StateDirectory=, LogsDirectory=, … and related directory settings (see below) also exclude the specific directories from the effect of ProtectSystem=. This setting is implied if DynamicUser= is set. This setting cannot ensure protection in all cases. In general it has the same limitations as ReadOnlyPaths=, see below. Defaults to off.

I haven't tried this, but try to set option to off


I would report this to upstream since the package has a strange way of handling this option.

matt1432 commented 2 months ago

Setting ProtectSystem to false did not fix my issue

rhoriguchi commented 2 months ago

Did you delete the 2 dirs before starting the service? Without that option, it should be able to see both dirs but it won't recreate that private symlink so you should delete it first but make a backup of your config.

matt1432 commented 2 months ago

I just tried deleting the 2 dirs and it didn't work either

GrumpyMeow commented 3 weeks ago

I tried the solution and it did work for me. Thanks!