Forwarding Dark Mode over SSH

24 Aug 2023 9:48 PM    nixos ssh macos
convert to local time zone


My Mac is set up to sync its light/dark mode with the local sunset, so it is in light mode through most of the day, and in dark mode later at night. All my apps sync with the system as well, including my terminal and terminal applications. However, what doesn’t sync is SSH sessions. If I SSH into one of my Mac Minis, and then run a command such as bat (which on my Mac is aliased to bat --theme=$(defaults read -globalDomain AppleInterfaceStyle &> /dev/null && echo Catppuccin-mocha || echo Catppuccin-latte) and thus respects dark mode), it always acts as if it is in dark mode (because the Mac Mini is in dark mode).

Clearly, I need to communicate the dark mode status from my computer to the computer I’m SSHing into. Fortunately, OpenSSH allows you to send environment variables using the SendEnv or SetEnv directives. I decided that I would send an environment variable named DARKMODE, so I added the following line to my ~/.ssh/config file1:

Host *
    SendEnv DARKMODE

I would have liked to use SetEnv and had the value of DARKMODE be set to the output of a command, but OpenSSH does not appear to support running commands, only fixed values. Because I was not able to set the value of DARKMODE in my SSH config, I needed to determine it each time SSH was run. To do this, I aliased ssh to DARKMODE=$(defaults read -globalDomain AppleInterfaceStyle &> /dev/null && echo YES || echo NO) ssh, which sets DARKMODE to either YES or NO depending on the current system theme, before falling back to the actual SSH command.

This is great for sending the current theme, but currently the server will reject it. This is because the default behavior for SSH servers is to accept a very limited list of environment variables — essentially TERM and a few LC_* variables, such as LC_ALL2.

To accept environment variables, you simply use the AcceptEnv directive in your sshd config. On NixOS, which is what all my Mac Minis are running, I simply change my SSH configuration to look like this:

# Enable the OpenSSH daemon.
services.openssh = {
  enable = true;
  settings = {
    PasswordAuthentication = false;
    KbdInteractiveAuthentication = false;
  };
  extraConfig = ''
    StreamLocalBindUnlink yes
    AcceptEnv DARKMODE
  '';
};

and run nixos-rebuild. Then all my terminal apps on the remote servers have a DARKMODE environment variable that they can check. For example, I have aliased bat on the Mac Minis to bat --theme=$([ "$DARKMODE" = "NO" ] && echo GitHub || echo default).


There is one major problem with this solution: if my Mac changes to or from Dark Mode (e.g., via a scheduled sync), the change is not propagated to any SSH sessions. I haven’t really looked into solutions to this problem, since I’m pretty sure any solution would need a program running in the background on either my Mac or the server I’m connecting to. Plus, I can just run export DARKMODE=YES or export DARKMODE=NO manually when necessary.

  1. For testing purposes, instead of modifying your SSH config file, you can add -o "SendEnv DARKMODE" to your SSH command invocations. 

  2. The default used to be that every LC_* variable was allowed, but this allowed clients to set variables with names like LC_MY_VAR, which were allowed, so the default was changed. Personally, it doesn’t seem like much of a security risk to me (since clients need to choose to do it, and no program is going to be reading random LC_WHATEVER variables), but whatever. 


Respond to this