My First Derivation - Splashtop Remote Access - Issue with libavcodec.so

Hello folks. Second post to the discourse and still really enjoying NixOS overall. There’s one application that I use for work that has FHS requirements and hasn’t been overly friendly on NixOS called Splashtop Remote Access.

I was able to get the application working using nix-alien though it seems like I should also be able to make it work declaratively by writing a derivation and although I’m linking in the same dependencies that work when run under nix-alien, it’s encountering a problem with libavcodec.so.

Here’s the default.nix file that nix-alien generated with the exception that I manually added ffmpeg in the targetPkgs, since nix-alien didn’t find this automatically and caused things to crash. It’s my understanding that ffmpeg provides libavcodec.so.

cat ~/.cache/nix-alien/6e43d9ad-dcb5-55e4-8e7f-43bbb4bdae7e/fhs-env/default.nix 
{ pkgs ? import
    (builtins.fetchTarball {
      name = "nixpkgs-unstable-20240410231924";
      url = "https://github.com/NixOS/nixpkgs/archive/1042fd8b148a9105f3c0aca3a6177fd1d9360ba5.tar.gz";
      sha256 = "sha256-3sbWO1mbpWsLepZGbWaMovSO7ndZeFqDSdX0hZ9nVyw=";
    })
    { }
}:

let
  inherit (pkgs) buildFHSUserEnv;
in
buildFHSUserEnv {
  name = "splashtop-business-fhs";
  targetPkgs = p: with p; [
    keyutils.lib
    libcap.lib
    libgcc.lib
    libsForQt5.qt5.qtbase.out
    libuuid.lib
    pulseaudio.out
    xdotool.out
    xorg.libxcb.out
    xorg.xcbutil.out
    xorg.xcbutilkeysyms.out
    zlib.out
    ffmpeg
  ];
  runScript = "/home/User/Downloads/splashtop/opt/splashtop-business/splashtop-business";
}

With that in mind, I began on my derivation. I know it can be improved to download the tarball from Splashtop but for development purposes, I have a copy of it downloaded locally.

cat default.nix 
{ pkgs ? import <nixpkgs> {} }:

pkgs.callPackage ./derivation.nix {}
cat derivation.nix 
{ stdenv, lib, dpkg, autoPatchelfHook, pkgs }:
let

  # Please keep the version x.y.0.z and do not update to x.y.76.z because the
  # source of the latter disappears much faster.
  version = "3.6.4.1";

  src = ./splashtop-business_Ubuntu_amd64.deb;

in stdenv.mkDerivation {
  name = "splashtop-business-linux-${version}";

  system = "x86_64-linux";

  inherit src;

  # Required for compilation
  nativeBuildInputs = [
    autoPatchelfHook # Automatically setup the loader, and do the magic
    dpkg
  ];

  # Required at running time
  buildInputs = with pkgs; [
    keyutils.lib
    libcap.lib
    libgcc.lib
    libsForQt5.qt5.qtbase.out
    libuuid.lib
    pulseaudio.out
    xdotool.out
    xorg.libxcb.out
    xorg.xcbutil.out
    xorg.xcbutilkeysyms.out
    zlib.out
    ffmpeg
  ];

  unpackPhase = "true";

  # Extract and copy executable in $out/bin
  installPhase = ''
    mkdir -p $out
    dpkg -x $src $out
    cp -av $out/opt/splashtop-business/* $out
    rm -rf $out/opt

    ln -s /tmp $out/log
  '';

  meta = with lib; {
    description = "Splashtop Business for Linux";
    homepage = "https://support-splashtopbusiness.splashtop.com/hc/en-us/articles/4404715685147-Download-Splashtop-Business-app-for-Linux";
    license = licenses.mit;
    maintainers = with lib.maintainers; [ ];
    platforms = [ "x86_64-linux" ];
  };
}

When I run the application, I am able to do most things but crashes when trying to render a remote desktop (presumably because of ffmpeg). This is an excerpt of the logs the program writes to the console during my testing that I see before the crash occurs.

[04/23/24 17:41:07] [STB.I]:[StartStreaming] Start streaming...
[04/23/24 17:41:07] [STB.I]:[Init] MediaPlayer Init
[04/23/24 17:41:07] [STB.I]:[operator()] tlv handle thread start
[04/23/24 17:41:07] [STB.I]:[operator()] Consume video data thread start
[04/23/24 17:41:07] [STB.I]:[operator()] Consume audio data thread start
[04/23/24 17:41:07] [STB.I]:[RecvAudioSampleFormat] RecvAudioSampleFormat
[04/23/24 17:41:07] [STB.I]:[InitAudioPlayer] InitAudioPlayer
[04/23/24 17:41:07] [STB.I]:[AudioPlayer] AudioPlayer created
[04/23/24 17:41:07] [STB.I]:[Init] AudioPlayer::Init
[04/23/24 17:41:07] [STB.I]:[Init] AudioDecoderCelt init success sample_rate: 48000, frame_size: 960
[04/23/24 17:41:07] [STB.I]:[InitVideoPlayer] InitVideoPlayer
[04/23/24 17:41:07] [STB.I]:[VideoPlayer] VideoPlayer created
[04/23/24 17:41:07] [STB.I]:[Init] VideoPlayer::Init
[04/23/24 17:41:07] [STB.I]:[Init] VideoDecoderFfmpeg init
terminate called after throwing an instance of 'std::runtime_error'
  what():  libavcodec.so: cannot open shared object file: No such file or directory
Failed to write crash dump
Aborted (core dumped)

Given that it works through nix-alien, this is more about learning and simplifying things on a go forward basis. Thanks!

❯ nix repl
Welcome to Nix 2.18.1. Type :? for help.

nix-repl> :l <nixpkgs>                                                          
Added 19860 variables.

nix-repl> :b pkgs.ffmpeg

This derivation produced the following outputs:
  bin -> /nix/store/9fwm1q0y8j1k0ff9khs8irf2j22mm67r-ffmpeg-6.0-bin
  data -> /nix/store/psm9ma325195370k8b64xnwg1g23rq3f-ffmpeg-6.0-data
  dev -> /nix/store/rprkp55j6zjqh1aq55q1k9jgyrxw1bi1-ffmpeg-6.0-dev
  doc -> /nix/store/c4l5gipi9ggzw4dzbaffz6afjj3589d1-ffmpeg-6.0-doc
  lib -> /nix/store/3zizvz8c3ngcpnf3waw247x56mka7fph-ffmpeg-6.0-lib
  man -> /nix/store/crcs2hdib11pq0r1j2y7hn6wbywysn9j-ffmpeg-6.0-man
  out -> /nix/store/xml1gv7r8zwchmpzhdxl2g9xssrhx14w-ffmpeg-6.0

nix-repl>                                       

~                                                                                                23:25:1
❯ ls /nix/store/3zizvz8c3ngcpnf3waw247x56mka7fph-ffmpeg-6.0-lib/lib/
libavcodec.so@            libavfilter.so@           libavutil.so@             libswresample.so@
libavcodec.so.60@         libavfilter.so.9@         libavutil.so.58@          libswresample.so.4@
libavcodec.so.60.3.100*   libavfilter.so.9.3.100*   libavutil.so.58.2.100*    libswresample.so.4.10.100*
libavdevice.so@           libavformat.so@           libpostproc.so@           libswscale.so@
libavdevice.so.60@        libavformat.so.60@        libpostproc.so.57@        libswscale.so.7@
libavdevice.so.60.1.100*  libavformat.so.60.3.100*  libpostproc.so.57.1.100*  libswscale.so.7.1.100*

You want the .lib output,

First and foremost, thank you for your prompt response and very thorough answer. Unfortunately, while I did change ffmpeg to ffmpeg.lib to target the .lib output and everything built fine, the actual libavcodec.so issue hasn’t gone away.

I also tried ffmpeg_5.lib and ffmpeg_4.lib, just to see if this was related to a version, but no success.

I do appreciate how your answer included your nix-repl commands/output as this is incredibly useful for the learning aspect.

Still toying around with it on my end to see if I can figure it out!

In a surprising turn of events, I think I found the missing piece. Based on information found in this post, I changed ffmpeg.lib back to ffmpeg and added the following line to my installPhase:

patchelf --add-needed libavcodec.so $out/splashtop-business

What I noticed previously, despite my inclusion of ffmpeg is that this line: libavcodec.so -> found: /nix/store/3zizvz8c3ngcpnf3waw247x56mka7fph-ffmpeg-6.0-lib/lib wasn’t appearing when the derivation built. It seems like most of the dependencies are required when the application starts up but libavcodec.so is probably dynamically loaded right before a remote desktop session.

And with that… it worked! I was able to connect to a working remote desktop session!

I also went ahead and changed ffmpeg back to ffmpeg.lib and rebuilt just to see if it would continue to work with a more specific output scope and it’s still working!

Obviously, I still need to add in the ability for this derivation to fetch the tarball right from Splashtop and it also needs to create an application shortcut for launching the app graphically, but it’s close!

Only “bug” I’m noticing is that every login acts like it’s the first and I believe it’s because the program is unable to write logged-in user specific configuration that it uses on future app loads. In short, it just doesn’t remember my username, password, or 2FA between launches. Not even a deal breaker, but something I’d probably like to find a fix for.

1 Like

congrats!

Btw did you see this thread? I wonder if you are having any problems with logs?

:smiley: Thanks!

Before the original post to the discourse I wrote a couple days ago, I was having issues with the log folder as not having this writable caused the app to crash nearly instantly. I did come across that exact link while trying to solve that issue.

Currently, the solution that is working for me having ln -s /tmp $out/log in the installPhase. The part of the app that remembers the previous login credentials doesn’t seem to rely on the log folder.

With that said, does building a derivation support a way of allowing the application to write files out, as required, that’s more permanent than sending it to /tmp? It seems like this may be doable with one of those FHS building functions, but that seems like kind of a heavy solution; or perhaps I’ve only seen heavy implementations be used.

As a side note, my derivation is working inside of my KDE Plasma VM but failed due to a QT plugin error when I tried on a different NixOS machine running Pantheon desktop. I’m sure it’s fixable but I’m not quite as done as I originally thought, haha!

Oh yeah, and I also adjusted it to pull the tarball directly from Splashtop’s website and it’s able to decompress and extract the .deb content declaratively :white_check_mark:

The problem with splashtop is that the binary is in the nix store, which is immutable, but the binary wants to write it’s logs relative the path of the binary. This makes no sense, and unless splash top has some config for this that leaves you SOL. I stand by my suggestions in that thread to symlink the logfile to /dev/stdout because then you can just redirect that wherever you want at runtime (or in a wrapper) or whatever. For the storage, it honestly depends what silliness splashtop is doing there. If it were me trying to make it would I’d be tempted to use strace or sysdig or similar to trap write syscalls to figure out where it is trying to stash auth tokens or whatever then get creative. Hard to say what to do before you know where it is trying to write to though.

So I revisited the other thread to explore the /dev/stdout approach more deeply. When I replace a softlink to /tmp with a softlink to /dev/stdout, I get the following output when trying to run the appliction:

ln -s /dev/stdout $out/log
$ ./result/splashtop-business 
terminate called after throwing an instance of 'spdlog::spdlog_ex'
  what():  Failed opening file /nix/store/mv9ykyiw0swijcm42rm2rpkf2498ibp0-splashtop-business-linux-3.6.4.1/log/splashtop-business.log for writing: Not a directory
Failed to write crash dump
Aborted (core dumped)
$ l
total 12M
dr-xr-xr-x    5 root root   4.0K Dec 31  1969 .
drwxrwxr-t 1919 root nixbld 2.5M Apr 30 16:16 ..
dr-xr-xr-x    3 root root   4.0K Dec 31  1969 lib
lrwxrwxrwx    1 root root     11 Dec 31  1969 log -> /dev/stdout
dr-xr-xr-x    2 root root   4.0K Dec 31  1969 script
-r-xr-xr-x    1 root root   8.6M Dec 31  1969 splashtop-business
dr-xr-xr-x    3 root root   4.0K Dec 31  1969 usr

Maybe I’m doing something wrong?

For the storage, it honestly depends what silliness splashtop is doing there.

In order to answer this question, I’m referencing the folder structure that gets created when running this application using nix-alien and it seems that Splashtop creates the following file that seems to hold things like the user Account, server regions, various uuids, etc. The path to this file is /opt/splashtop-business/config/src_user_settings.ini. To your point, it totally seems to be relative to the binary and the /nix/store being read-only would prevent that. Given this file path, any thoughts on how the derivation could be modified to allow for this file? [Edit: Perhaps Overlays is what I want?]

I don’t think you are doing anything wrong per-se, it just seems like some of the paths seem subtly different. It seems like you need ln -s /dev/stdout $out/log/splashtop-business.log (and ofc you need to make the log dir). I guess one horrific option would be to make dangling links in the derivation like ln -s /var/lib/splashtop-business/config $out/opt/splashtop-business/config (assuming that I’ve understood your post correctly) and then invoke splashtop from a wrapper that makes /var/lib/splashtop-business/config before invoking the binary. You could do similarly for the log directory, then you wouldn’t need to care about knowing all the log file names in advance.