Writing a service for protonmail-bridge

I am currently trying to write my first NixOS service for the protonmail-bridge package. Because the program is quite monolithic, I am having trouble deciding how to approach it.

This is what I have so far

{ config, lib, pkgs, ... }:

with lib;
let
  cfg = config.services.protonmail-bridge;
in
{
  ##### interface
  options = {
    services.protonmail-bridge = {
      enable = mkOption {
        type = types.bool;
        default = false;
        description = "Whether to enable the Bridge.";
      };

      user = mkOption {
        type = types.str;
        description = "The user which should run the bridge";
      };

      nonInteractive = mkOption {
        type = types.bool;
        default = false;
        description = "Start Bridge entirely noninteractively";
      };

      logLevel = mkOption {
        type = types.enum [ "panic" "fatal" "error" "warn" "info" "debug" "debug-client" "debug-server" ];
        default = "info";
        description = "The log level";
      };

      trustCertificate = mkOption {
        type = types.bool;
        default = true;
        description = "Trust the self-signed certificate generated by the Bridge";
      };

    };
  };

  ##### implementation
  config = mkIf cfg.enable {

    environment.systemPackages = [ pkgs.protonmail-bridge ];
    
    systemd.services.protonmail-bridge = {
      description = "Protonmail Bridge";
      after = [ "network.target" ];
      wantedBy = [ "default.target" ];
      path = [ pkgs.pass ];
      serviceConfig = {
        User = "${cfg.user}";
        Restart = "always";
        ExecStart = "${pkgs.protonmail-bridge}/bin/protonmail-bridge --no-window --log-level ${cfg.logLevel}" + optionalString (cfg.nonInteractive) " --noninteractive";
      };
    };
  };
}

There are several challenges I am facing:

  1. The protonmail bridge stores credentials using pass or gnome-keyring. Because these are usually stored in the user’s personal keyring, how do we express this in the service description?
  2. How do you deal with the fact that the protonmail bridge generates a self-signed certificate that must be trusted by the system for it to be useable in MUAs. I currently copy the certificate manually after it is generated trust it by adding it to security.pki.certificates.
1 Like

protonmail-bridge is probably better off as a home-manager module, or a NixOS module that generates a systemd user level service.

Running protonmail-bridge as a system level systemd service makes running more than a single instance impossible and doesn’t let you configure it as nice as home-manager would, IMO.

To answer your questions, though:

  1. Run as a user level systemd service as I mentioned above and I believe this problem is solved.
  2. Can you provide self signed certificates for protonmail-bridge to use instead of generating? This would be a convenient way to solve that problem.
1 Like

Thanks for the insight. I switched to a user service and wrote a module for home-manager (see below). The keyring/password store can now be accessed without any problems and the service works as expected. I looked into it some more and it should be possible to provide a certificate yourself for the bridge. Knowing this, how could one trust the certificate without manually needing to add it to security.pki.certificates

Also is there a way to add a “dependency” to the module to make sure that the user either has pass or gnome-keyring installed before they are able to enable this service?

{ config, lib, pkgs, ... }:

with lib;
let
  cfg = config.services.protonmail-bridge;
  #Still need to integrate more closely with the email management capabilities of home-manager
in
{
  ##### interface
  options = {
    services.protonmail-bridge = {
      enable = mkOption {
        type = types.bool;
        default = false;
        description = "Whether to enable the Bridge.";
      };

      nonInteractive = mkOption {
        type = types.bool;
        default = false;
        description = "Start Bridge entirely noninteractively";
      };

      logLevel = mkOption {
        type = types.enum [ "panic" "fatal" "error" "warn" "info" "debug" "debug-client" "debug-server" ];
        default = "info";
        description = "The log level";
      };

    };
  };

  ##### implementation
  config = mkIf cfg.enable {

    home.packages = [ pkgs.protonmail-bridge ];
    
    systemd.user.services.protonmail-bridge = {
      Unit = {
        Description = "Protonmail Bridge";
        After = [ "network.target" ];
      };

      Service = {
        Restart = "always";
        ExecStart = "${pkgs.protonmail-bridge}/bin/protonmail-bridge --no-window --log-level ${cfg.logLevel}" + optionalString (cfg.nonInteractive) " --noninteractive";
      };

      Install = {
        WantedBy = [ "default.target" ];
      };
    };
  };
}

3 Likes

Hello there! I am trying to replicate this to integrate it in my configuration. Did you manage to solve the certificate trust without manual steps?

1 Like

Hello! Anyone had success installing the bridge on Nix Os? Is this something that could be merged into home-manager?

Hi, sorry for the very late reply. I’ve been able to use protonmail-bridge since about May 2023 to send and receive mail under NixOS. I’m on unstable:

OS: NixOS 24.05.20231129.e92039b (Uakari) x86_64 
Host: Supermicro X8DA3 
Kernel: 6.6.2 
Packages: 1799 (nix-system), 6554 (nix-user) 
Shell: bash 5.2.15 
DE: Plasma 5.27.9 (Wayland) 
WM: .kwin_wayland_w 
Theme: Breeze [GTK2/3] 
Icons: breeze [GTK2/3] 
Terminal: .konsole-wrappe 
# ...

I could never get a configuration using systemd’s systemctl --user [start][stop] ... to work reliably using home-manager, so I opted instead for bash scripts that (auto)start/stop the bridge upon my kde login. E.g., my nix file to start the bridge is /etc/nixos/bash/pm-bridge-up.nix

{ pkgs, ... }:

let
  pm-bridge-up = pkgs.writeShellScriptBin "pm-bridge-up" ''
    PID=$(pgrep protonmail-brid)
    if [ -z "$PID" ]
    then
      echo "launching background process protonmail-bridge CLI ..."
      protonmail-bridge --no-window --noninteractive --log-level info &
      sleep 10
      PID=$(pgrep protonmail-brid)
      if [ -z "$PID" ]
      then
        echo "no protonmail-bridge process"
      else
        echo "protonmail-bridge process = $PID"
        echo -n "Testing bridge ... "
        nc -z -v 127.000.001 1143
      fi
    else
      echo "bridge already active (process $PID) ..."
    fi
  '';
in {
  environment.systemPackages = [ pm-bridge-up ];
}

This file sits with a bunch of similar bash scripts in /etc/nixos/bash with a /etc/nixos/bash/default.nix of

{...}: {
  imports = [
# ...
    ./pm-bridge-dn.nix
    ./pm-bridge-up.nix
    ./pm-bridge-st.nix
    ./helloWorld.nix
  ];
}

and that in turn gets incoporated as one of many modules in /etc/nixos/flake.nix

{
  description = "flake for myNixOS";

  inputs = {
    nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable";
    home-manager.url = "github:rycee/home-manager/master";
    home-manager.inputs.nixpkgs.follows = "nixpkgs";
  };
  outputs = inputs@{ nixpkgs, home-manager, ... }: {
    nixosConfigurations = {
      myNixOS = nixpkgs.lib.nixosSystem {
        system = "x86_64-linux";
        modules = [
          (home-manager.nixosModules.home-manager)
          ./hardware-configuration.nix
# ...
# this picks up /etc/nixos/bash/default.nix which imports the pm-bridge scripts:
          ./bash
# ...
          ({ config, lib, pkgs, ... }: {
# NixOS configuration starts here:
            nix = {
              settings = {
                experimental-features = [ "nix-command" "flakes" ];
              };
              extraOptions = ''
                extra-experimental-features = nix-command
              '';
            };
# ...
            environment.systemPackages = with pkgs; [
              gitFull
              (perl.withPackages (ps: [ ps.LaTeXML ps.LatexIndent ps.Later ]))
# ...
            ];
# ...
# home-manager config starts here:
            home-manager.users.rolf = { pkgs, ... }: {
              home.stateVersion = "22.05";
              home.packages = with pkgs; [
# ...
                protonmail-bridge
# ...
              ];
              nixpkgs.config.allowUnfree = true;
              programs = {
                nix-index.enable = true;
# ...
              }; # end program declarations
            }; # end declarations for user = rolf
          }) # end NixOS config
        ]; # end modules
      };
    };
  };
}

I’m sure to more experienced nix users the above looks like crap, but it does work for me. Sorry if this is more than you needed to know, but thought I’d err on the side of completeness, after frustration in trying to use other people’s stuff as templates, and not knowing enough nix to incorporate the inevitable missing parts.

I’m trying to incorporate this with my system, and I have a really weird problem. On boot the service runs, but I see in the status of the service that there was some sort of error with loading/creating the vault so the bridge client doesn’t actually work. however If I restart the service, it works perfectly. I have no clue why such behavior is happening where it errors on boot but works on service restart…

Please excuse the late reply.

On boot the service runs, but I see in the status of the service that there was some sort of error with loading/creating the vault so the bridge client doesn’t actually work.

I’ve always assumed that the --noninteractive switch (part of the bridge command) means that protonmail-bridge requires access to user credentials/passwords without intervention. Are you starting this service as user, i.e.,
systemctl --user start ...?
I wonder if the failure during boot reflects the fact that the service needs access to the vault associated with your user, whereas if you restart it (presumably in a shell after login), the service then has access to your user files?

According to the ProtonMail bridge page the bridge only supports pass or gnome-keyring as secret storage backends. Maybe they are not available during the first attempt to start the service?

@JesusMtnez I’ve seen you use a 20 second sleep workaround to get the protonmail-bridge service working.
I’m curious if you’ve debugged the issue further and might be stumbling over this issue too?

Sorry for the late reply guys,

@rsa4046 @don.dfh I noticed that the services wouldn’t try to start until I logged into my user, and since it worked when I restarted it after logging in I decided to add 10-20 second sleep delay which worked as @don.dfh mentioned. I think for now this is what I will go with.

1 Like