Casual Nixpkgs contributions

Referring to the discussion here How many people are paid to work on Nix/Nixpkgs? I open this topic to be able do to casual nixpkgs contributions. The idea is to lower the threshold to contribute to Nixpkgs for people like me who are not a dev, do not have a Github account, do not check out nixpkgs via git locally, are merely users, etc. etc etc but still want to share something so now and then.

Here is my first casual contribution. A sponsored PR (=someone who wants to shape it into an actual PR) would be appreciated.

{ stdenv, fetchFromGitHub, python3 }:

stdenv.mkDerivation rec {
  pname = "ff2mpv-host";
  version = "3.6";

  src = fetchFromGitHub {
    owner = "woodruffw";
    repo = "ff2mpv";
    rev = "v${version}";
    sha256 = "0dpqd4vis4c0pvdfpkgflc6ccdm98jils3jp0ncz1nvam4swsr1x";
  };

  buildInputs = [ python3 ];

  installPhase = ''
     runHook preInstall
     install -D ff2mpv.py $out/share/ff2mpv.py
     substitute ff2mpv.json $out/share/ff2mpv.json \
       --replace /home/william/scripts/ff2mpv $out/share/ff2mpv.py

     nativeMessagingPaths=(
       /lib/mozilla/native-messaging-hosts
    #   /etc/opt/chrome/native-messaging-hosts
    #   /etc/chromium/native-messaging-hosts
    #   /etc/vivaldi/native-messaging-hosts
     )

     for manifestDir in "''${nativeMessagingPaths[@]}"; do
       install -d $out$manifestDir
       ln -s $out/share/ff2mpv.json $out$manifestDir/
     done

    runHook postInstall
  '';

  meta = with stdenv.lib; {
    description = "Host app for the WebExtension ff2mpv";
    homepage = "https://github.com/woodruffw/ff2mpv";
    license = licenses.mit;
  };
}

2 Likes

You don’t have to set the maintainers field, considering the circumstances. Though I wonder: what you mean by “sponsor”?

Do not override phases, without runHook before and after it. Read: https://github.com/jtojnar/nixpkgs-hammering/blob/193d2108495b6c53c591f2f99de981153f218003/explanations/explicit-phases.md

Usually web extension’s json files are installed in several locations, Consider for instance:

You can use only the Firefox path if you wish.

Use pname = "ff2mpv and the name attribute will be derived automatically.

Does the executable eventually gets wrapped appropriately? For this simple derivation, you can use dontBuild = true and patchShebangs.

Though I wonder: what you mean by “sponsor”?

Presumably https://en.wikipedia.org/wiki/Sponsor_(legislative)

1 Like

Thanks for the feedback, I will process it and update the original message.
With sponsor I mean something similar as ‘non maintainer upload’ in Debian world. Someone who shapes it into a merge-able PR on behalf of the casual contributor (and can take credit if that matters)

Updated:

  • changed name+$version -> pname
  • runHooks added
  • kept buildInputs python3 otherwise patchshebangs doesn’t do anything
  • kept it Firefox only, because the author only markets the extension for Firefox, and I did not test it in other browsers
  • made it look like the passff-host derivation
2 Likes

thanks both of you @kvtb and @doronbehar but wow, this looks awfully close to a Pull Request against nixpkgs minus the actual diff and all the goodies github brings.

Especially with hacktoberfest currently going on, these kind of additions make good first contributions.
@kvtb you should really think of filing a PR for this!

Apart from that, you can always start your own Nix User Repository ( NUR ) if you just want to give access to the derivations you’ve wrote. there is even a search for this at nur.nix-community.org which contains contributions of packages which may not fulfill the quality standards of nixpkgs.

3 Likes

Looking closer, the license for this software is not the MIT license, but the MIT license with additional restrictions (https://github.com/woodruffw/ff2mpv/blob/ef1ad015871fc2b876e8c89b7a31ccb62c583cd6/LICENSE).

I know I offered to sponsor (in the sense of: convert to a PR on your behalf) your contributions earlier, but I’m not sure I find this particular one suitable, so I think I’ll pass in this particular case. Sorry!

1 Like

sadness, the license was updated after I wrote the first version of the derivation a few months a go.

Anyway, here’s another one, for SeaweedFS a distributed file system
Ideally this also comes with NixOS service definitions and options and such but I’ll leave that to those who know what they are doing.

So for nixpkgs, if someone sees the added value of SeaweedFS as new package:

 { lib, fetchFromGitHub, buildGoModule }:

 buildGoModule rec {
   pname = "seaweedfs";
   version = "2.07";

   src = fetchFromGitHub {
     owner = "chrislusf";
     repo = "seaweedfs";
     rev = "${version}";
     sha256 = "0yrfpj4ph9f321vqfn1zadv44pqa3ivjq9rx6gsz9hlv50gfaqn1";
   };

   vendorSha256 = "1ysqagi4y25bi84h5fhkdimnsigy43klf6hrcqn7q75382fb4bzn";

   subPackages = [ "weed" ];

   meta = with lib; {
     description = "Simple and highly scalable distributed file system";
     homepage = "https://github.com/chrislusf/seaweedfs";
     license = licenses.asl20;
     #   maintainers = with maintainers; [ sponsor-name-here ];
     platforms = platforms.linux ++ platforms.darwin;
   };
 }

Looks interesting - though if you see the added value that is good enough for me :wink: . Converted to PR at:

3 Likes

I notice it has been merged today, thanks for making this sponsor-based PR approach work, @raboof !

1 Like

@kvtb This is a nice idea. Though, I fear the odds of effort dynamics prevent it from spreading.

Maybe a slight variant could work, though: sometimes somebody does a PR (that somebody can be me) but needs a little more tutoring to get it into shape (as far as even allowing direct push onto the PR branch).

Currently, I need tutoring on:

I left it in “draft”, but that’s not very expressive and concise.

Hi all,

apparently today I’m here exactly 1 year.
Let’s celebrate. I’ve managed to create a NixOS service for seaweedfs, the package of which the inclusion was done by @raboof

I’d say it’s 99% ready, but since I’ve never created a NixOS service before, I’m looking forward to feedback. I will update this post accordingly

{ config, lib, pkgs, ... }:
with lib;
let

  seaweedfs = pkgs.seaweedfs;

  user = "seaweedfs";
  group = "seaweedfs";
  cfg = config.services.seaweedfs;
  enabledVolumes = filterAttrs (_: v: v.enable) cfg.volumes;
  anyEnabled = cfg.master.enable || cfg.filer.enable || cfg.webdav.enable
    || enabledVolumes != { };

  mkCmdLineArguments = mapAttrsToList (option: value:
    if isBool value then
      "-${option}"
    else
      "-${option}=${
        if isList value then
          builtins.concatStringsSep "," value
        else
          toString value
      }");

  mkWeedExec = subcmd: options:
    (toString ([ "${seaweedfs}/bin/weed" subcmd ] ++ mkCmdLineArguments
      ((removeAttrs options [ "enable" "extraConfig" ])
        // (if options ? "extraConfig" then
          (removeAttrs options.extraConfig (builtins.attrNames options))
        else
          { }))));

  mkExtraConfigOption = subcmd:
    mkOption {
      default = { };
      type = with types; attrs;
      description = ''
        Additional configuration, see output of 'weed ${subcmd} --help' for attributes.
        Do not define settings for flags for which explicit configuration options exist, these will be ignored.
      '';
    };

  mkPortOption = defaultPort:
    mkOption {
      default = defaultPort;
      type = with types; uniq port;
      description = "Http listen port";
    };

  mkServerListOption = subcmd:
    mkOption {
      default = [ "localhost:${toString cfg.${subcmd}.port}" ];
      type = with types; listOf str;
      description = "List of ${subcmd} servers (host/ip:port)";
    };

  mkVolumeService = id: options:
    nameValuePair "seaweedfs-volume-${id}" {
      wantedBy = [ "multi-user.target" ];
      after = [ "network.target" ]
        ++ optional cfg.master.enable "seaweedfs-master.service";
      description = "SeaweedFS volume - ${id}";
      unitConfig.ConditionPathIsDirectory = options.dir;
      serviceConfig = {
        User = user;
        Group = group;
        ExecStart = mkWeedExec "volume" options;
        KillSignal = "SIGTERM";
      };
    };

  mkVolumeOptions = { id, ... }: {
    options = {
      enable = mkEnableOption "SeaweedFS volume server";
      port = mkPortOption 8080;
      mserver = mkServerListOption "master";
      extraConfig = mkExtraConfigOption "volume";

      dir = mkOption {
        default = [ "/tmp" ];
        type = with types; listOf path;
        description = ''
          One or more directories to store data files.
          These must exist before the volume server service is started,
          and must be owned by ${user}:${group}.'';
      };
    };
  };

in {

  ###### interface

  options = {
    services.seaweedfs = {

      master = {
        enable = mkEnableOption "SeaweedFS master server";
        port = mkPortOption 9333;
        extraConfig = mkExtraConfigOption "master";

      };

      filer = {
        enable = mkEnableOption "SeaweedFS file server";
        port = mkPortOption 8888;
        master = mkServerListOption "master";
        extraConfig = mkExtraConfigOption "filer";

      };

      volumes = mkOption {
        default = { };
        type = with types; attrsOf (submodule mkVolumeOptions);
      };

      webdav = {
        enable = mkEnableOption "SeaweedFS webdav server";
        port = mkPortOption 7333;
        filer = mkServerListOption "filer";
        extraConfig = mkExtraConfigOption "webdav";

      };
    };
  };

  ###### implementation

  config = mkIf anyEnabled {
    environment.systemPackages = [ seaweedfs ];
    users.users.${user} = {
      description = "SeaweedFS user";
      isSystemUser = true;
    };
    users.groups.${group} = { };
    systemd.services = (mapAttrs' mkVolumeService enabledVolumes) // {
      seaweedfs-master = mkIf cfg.master.enable {
        wantedBy = [ "multi-user.target" ];
        after = [ "network.target" ];
        description = "SeaweedFS master";
        serviceConfig = rec {
          User = user;
          Group = group;
          StateDirectory = "seaweedfs/master";
          ExecStart = (mkWeedExec "master" cfg.master)
            + " -mdir=/var/lib/${StateDirectory}";
          KillSignal = "SIGTERM";
        };
      };

      seaweedfs-filer = mkIf cfg.filer.enable {
        wantedBy = [ "multi-user.target" ];
        after = [ "network.target" ]
          ++ optional cfg.master.enable "seaweedfs-master.service";
        description = "SeaweedFS filer";
        serviceConfig = rec {
          User = user;
          Group = group;
          WorkingDirectory = "/var/lib/${StateDirectory}";
          StateDirectory = "seaweedfs/filer";
          ExecStart = mkWeedExec "filer" cfg.filer;
          KillSignal = "SIGTERM";
        };
      };

      seaweedfs-webdav = mkIf cfg.webdav.enable {
        wantedBy = [ "multi-user.target" ];
        after = [ "network.target" ]
          ++ optional cfg.filer.enable "seaweedfs-filer.service";
        description = "SeaweedFS webdav";
        serviceConfig = {
          User = user;
          Group = group;
          ExecStart = mkWeedExec "webdav" cfg.webdav;
          KillSignal = "SIGTERM";
        };
      };
    };
  };
}

Here’s an example how to use it:

...
  services.seaweedfs.master.enable = true;
  services.seaweedfs.master.extraConfig.resumeState = true;
  services.seaweedfs.filer.enable = true;
  services.seaweedfs.webdav.enable = true;
  services.seaweedfs.webdav.extraConfig = {
    disk = "hdd";
    cacheCapacityMB = 100;
  };
  services.seaweedfs.volumes = {
    default = {
      enable = true;
      dir = [ "/seaweed-volume-sda" "/mnt/backup/seaweedfs/volume-sdb" ];
      #  mserver = [ "localhost:${config.services.seaweedfs.master.port}" ];
    };
  };

  networking.firewall.allowedTCPPorts = [
    config.services.seaweedfs.master.port # HTTP
    19333 # gRPC
    config.services.seaweedfs.volumes.default.port
    18080
    config.services.seaweedfs.filer.port
    18888
  ];
...

:tada:

Cool! I’m afraid I don’t have any experience with NixOS services, so I’m afraid I can’t really be of help here.

gnucap


{ lib, stdenv, fetchurl }:

stdenv.mkDerivation rec {
  pname = "gnucap";
  version = "20210107";

  src = fetchurl {
    url = "https://git.savannah.gnu.org/cgit/gnucap.git/snapshot/${pname}-${version}.tar.gz";
    sha256 = "12rlwd4mfc54qq1wrx5k8qk578xls5z4isf94ybkf2z6qxk4mhnj";
  };

  doCheck = true;

  meta = with lib; {
    description = "Gnu Circuit Analysis Package";
    longDescription = ''
Gnucap is a modern general purpose circuit simulator with several advantages over Spice derivatives.
It performs nonlinear dc and transient analyses, fourier analysis, and ac analysis.
    '';
    homepage = "http://www.gnucap.org/";
    changelog = "https://git.savannah.gnu.org/cgit/gnucap.git/plain/NEWS?h=v${version}";
    license = licenses.gpl3Plus;
    platforms = platforms.all;
  };
}

While building I see:

warning: either libtermcap or libreadline is missing - building without readline

Is that OK? Submitted as gnucap: init at 20210107 by raboof · Pull Request #117039 · NixOS/nixpkgs · GitHub

Here’s a derivation for ff2mpv-go, an alternative implementation, released in the public domain, so no license issues this time!

{ mpv, lib, fetchgit, buildGoPackage }:

buildGoPackage {
  name = "ff2mpv-go";

  src = fetchgit {
    url = "https://git.clsr.net/util/ff2mpv-go";
    sha256 = "1rzw8r29sj1437r7ncx061n6k69dgd8pv55y2h0is74ihkdr0q4l";
  };

  goPackagePath = "git.clsr.net/util/ff2mpv-go";

  patchPhase = ''
    runHook prePatch
    sed -i 's#string{"mpv",#string{"${mpv}/bin/mpv",#' ff2mpv.go
    runHook postPatch
     '';

  installPhase = ''
    runHook preInstall

    mkdir -p $out
    dir="$NIX_BUILD_TOP/go/bin"
    [ -e "$dir" ] && cp -r $dir $out

    $out/bin/ff2mpv-go --manifest > ff2mpv.json
    install -Dt $out/share ff2mpv.json

    nativeMessagingPaths=(
           /lib/mozilla/native-messaging-hosts
        #   /etc/opt/chrome/native-messaging-hosts
        #   /etc/chromium/native-messaging-hosts
        #   /etc/vivaldi/native-messaging-hosts
    )

    for manifestDir in "''${nativeMessagingPaths[@]}"; do
        install -d $out$manifestDir
        ln -s $out/share/ff2mpv.json $out$manifestDir/
    done

    runHook postInstall
  '';

  meta = with lib; {
    description = "Host app for the WebExtension ff2mpv, written in Go";
    homepage = "https://git.clsr.net/util/ff2mpv-go/about/";
    license = licenses.publicDomain;
    platforms = platforms.linux ++ platforms.darwin;
  };
}