{ config, options, lib, pkgs, ... }: let inherit (lib) attrValues filter filterAttrs getExe hasSuffix head id length listToAttrs mkDefault mkEnableOption mkIf mkOption mkPackageOption replaceStrings ; inherit (lib.types) listOf nullOr str submodule ; cfg = config.programs.radicle; opt = options.programs.radicle; configFile = rec { format = pkgs.formats.json { }; name = "config.json"; path = pkgs.runCommand name { nativeBuildInputs = [ cfg.cli.package ]; } '' mkdir keys echo "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAID/this/is/not/a/real/key/only/a/placeholder" \ > keys/radicle.pub cp ${format.generate name cfg.settings} ${name} RAD_HOME=$PWD rad config cp ${name} $out ''; }; publicExplorerSuffix = "$rid$path"; in { meta.maintainers = with lib.maintainers; [ lorenzleutgeb matthiasbeyer ]; options = { programs.radicle = { enable = mkEnableOption "Radicle"; cli.package = mkPackageOption pkgs "radicle-node" { }; uri = { rad = { browser = { enable = mkOption { description = "Whether to enable `rad:`-URI handling by web browser"; default = (hasSuffix publicExplorerSuffix cfg.settings.publicExplorer) && pkgs.stdenv.hostPlatform.isLinux; defaultText = "`true` if a suitable public explorer is detected."; example = false; }; preferredNode = mkOption { type = str; description = "The hostname of an instance of `radicle-node`, reachable via HTTPS."; default = "iris.radicle.xyz"; example = "radicle-node.example.com"; }; }; vscode = { enable = mkEnableOption "`rad:`-URI handling by VSCode"; extension = mkOption { type = str; description = "The unique identifier of the VSCode extension that should handle `rad:`-URIs."; default = "radicle-ide-plugins-team.radicle"; }; }; }; web-rad = let detected = let detectionList = attrValues ( filterAttrs (n: _: config.programs.${n}.enable) { librewolf = "librewolf.desktop"; firefox = "firefox.desktop"; chromium = "chromium-browser.desktop"; } ); in lib.optionals (detectionList == [ ]) (head detectionList); in { enable = mkEnableOption "`web+rad:`-URI handling by web browser"; browser = mkOption { description = '' Name of the XDG Desktop Entry for your browser. LibreWolf, Firefox and Chromium configured via home-manager will be detected automatically. The value of this option should likely be the same as the output of `xdg-mime query default x-scheme-handler/https`. ''; type = nullOr str; default = detected; defaultText = "Automatically detected browser."; example = "brave.desktop"; }; }; }; settings = mkOption { default = { }; description = "Radicle configuration, written to `~/.radicle/config.json."; type = submodule { freeformType = configFile.format.type; options = { publicExplorer = mkOption { type = str; description = "HTTPS URL pattern used to generate links to view content on Radicle via the browser."; default = "https://app.radicle.xyz/nodes/$host/$rid$path"; example = "https://radicle.example.com/nodes/seed.example.com/$rid$path"; }; node = { alias = mkOption { type = str; description = '' Human readable alias for your node. When running 'rad auth' for the first time, you need to use the exact same alias, otherwise 'rad auth' will fail because it cannot write the configuration file in your home directory (which is generated by nix). ''; default = config.home.username; defaultText = lib.literalExpression "config.home.username"; }; listen = mkOption { type = listOf str; description = "Addresses to bind to and listen for inbound connections."; default = [ ]; example = [ "127.0.0.1:58776" ]; }; }; }; }; }; }; }; config = mkIf cfg.enable { assertions = [ { assertion = !pkgs.stdenv.hostPlatform.isLinux -> ( (filter id [ cfg.uri.rad.browser.enable cfg.uri.rad.vscode.enable ]) == [ ] ); message = "`rad:`-URI handlers are only supported on Linux."; } { assertion = cfg.uri.web-rad.enable -> cfg.uri.web-rad.browser != null; message = "Could not detect preferred browser. Please set `${toString opt.uri.web-rad.browser}`."; } { assertion = 1 >= length ( filter id [ cfg.uri.rad.browser.enable cfg.uri.rad.vscode.enable ] ); message = "At most one `rad:`-URI handler may be enabled."; } { assertion = cfg.uri.rad.browser.enable -> hasSuffix publicExplorerSuffix cfg.settings.publicExplorer; message = "${opt.uri.rad.browser.enable} is only compatible with a public explorer URL ending in '${publicExplorerSuffix}' but '${cfg.settings.publicExplorer}' does not end with '${publicExplorerSuffix}'."; } ]; home = { packages = [ cfg.cli.package ]; file.".radicle/${configFile.name}".source = configFile.path; }; xdg = { mimeApps.defaultApplications = { "x-scheme-handler/rad" = let isEnabled = cfg.uri.rad.browser.enable || cfg.uri.rad.vscode.enable; handlerTarget = if cfg.uri.rad.browser.enable then "rad-to-browser.desktop" else if cfg.uri.rad.vscode.enable then "rad-to-vscode.desktop" else throw "unreachable"; handler = mkDefault handlerTarget; in mkIf isEnabled handler; "x-scheme-handler/web+rad" = mkIf cfg.uri.web-rad.enable (mkDefault cfg.uri.web-rad.browser); }; desktopEntries = let mkHandler = { name, shortName, prefix, }: { name = "Open Radicle URIs with ${name}"; genericName = "Code Forge"; categories = [ "Development" "RevisionControl" ]; exec = getExe ( pkgs.writeShellApplication { name = "rad-to-${shortName}"; meta.mainProgram = "rad-to-${shortName}"; text = ''xdg-open "${prefix}$1"''; } ); mimeType = [ "x-scheme-handler/rad" ]; noDisplay = true; }; toHandler = v: { name = "rad-to-${v.shortName}"; value = mkIf cfg.uri.rad.${v.shortName}.enable (mkHandler v); }; in listToAttrs ( map toHandler [ { name = "Web Browser"; shortName = "browser"; prefix = replaceStrings [ "$host" publicExplorerSuffix ] [ cfg.uri.rad.browser.preferredNode "" ] cfg.settings.publicExplorer; } { name = "VSCode"; shortName = "vscode"; prefix = "vscode://${cfg.uri.rad.vscode.extension}/"; } ] ); }; }; }