radicle: init

This commit is contained in:
Lorenz Leutgeb 2024-05-15 21:44:55 +02:00
parent 44677a1c96
commit 6bf5c0c073
6 changed files with 435 additions and 0 deletions

View file

@ -0,0 +1,261 @@
{ 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)
attrsOf bool enum ints listOf nullOr oneOf package path 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
echo lel
exit 1
cp ${name} $out
'';
};
freeform = options:
mkOption {
default = { };
type = submodule {
inherit options;
freeformType = configFile.format.type;
};
};
publicExplorerSuffix = "$rid$path";
in {
meta.maintainers = with lib.maintainers; [ lorenzleutgeb ];
options = {
programs.radicle = {
enable = mkEnableOption "Radicle";
cli = { package = mkPackageOption pkgs "radicle-cli" { }; };
remote-helper = {
package = mkPackageOption pkgs "radicle-remote-helper" { };
};
uri = {
rad = {
browser = {
enable = mkOption {
description =
"Whether to enable `rad:`-URI handling by web browser";
default =
hasSuffix publicExplorerSuffix cfg.settings.publicExplorer;
defaultText = "`true` if a suitable value for ${
toString opt.settings.publicExplorer
} is detected.";
example = false;
};
preferredNode = mkOption {
type = str;
default = "seed.radicle.garden";
};
};
vscode = {
enable = mkEnableOption "`rad:`-URI handling by VSCode";
extension = mkOption {
type = str;
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 if detectionList == [ ] then null else 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;
example = "brave.desktop";
default = detected;
};
};
};
environment = mkOption {
type = attrsOf (nullOr (oneOf [ str path package ]));
default = {
RUST_LOG = "info";
RUST_BACKTRACE = "1";
};
};
node = {
args = mkOption {
type = str;
default = "--listen 0.0.0.0:8776 --force";
};
package = mkPackageOption pkgs "radicle-node" { };
};
httpd = {
args = mkOption {
type = str;
default = "--listen 127.0.0.1:8080";
};
package = mkPackageOption pkgs "radicle-httpd" { };
};
settings = freeform {
publicExplorer = mkOption {
type = str;
default = "https://app.radicle.xyz/nodes/$host/$rid$path";
};
preferredSeeds = mkOption {
type = listOf str;
default = [
"z6MkrLMMsiPWUcNPHcRajuMi9mDfYckSoJyPwwnknocNYPm7@seed.radicle.garden:8776"
"z6Mkmqogy2qEM2ummccUthFEaaHvyYmYBYh3dbe9W4ebScxo@ash.radicle.garden:8776"
];
};
web = freeform {
pinned = freeform {
repositories = mkOption {
type = listOf str;
default = [ ];
};
};
};
cli = freeform {
hints = mkOption {
type = bool;
default = true;
};
};
node = freeform {
alias = mkOption {
type = str;
default = config.home.username;
};
network = mkOption {
type = str;
default = "main";
};
relay = mkOption {
type = bool;
default = true;
};
peers = freeform {
type = mkOption {
type = enum [ "static" "dynamic" ];
default = "dynamic";
};
target = mkOption {
type = ints.unsigned;
default = 8;
};
};
workers = mkOption {
type = ints.unsigned;
default = 8;
};
policy = mkOption {
type = enum [ "allow" "block" ];
default = "block";
};
scope = mkOption {
type = enum [ "all" "followed" ];
default = "all";
};
};
};
};
};
config = mkIf cfg.enable {
assertions = [
{
assertion = cfg.uri.web-rad.enable -> cfg.uri.web-rad.browser != null;
message = "Could not detect preferred browser. Please set `${
builtins.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 ${cfg.settings.publicExplorer} ending in '${publicExplorerSuffix}.";
}
];
home = {
packages = [ cfg.cli.package cfg.remote-helper.package ];
file.".radicle/${configFile.name}".source = configFile.path;
};
programs.git.enable = mkDefault true;
services.ssh-agent.enable = mkDefault true;
xdg = {
mimeApps.defaultApplications = {
"x-scheme-handler/rad" =
mkIf (cfg.uri.rad.browser.enable || cfg.uri.rad.vscode.enable)
(mkDefault ("rad-to-" + (if cfg.uri.rad.browser.enable then
"browser"
else if cfg.uri.rad.vscode.enable then
"vscode"
else
throw "unreachable") + ".desktop"));
"x-scheme-handler/web+rad" =
mkIf cfg.uri.web-rad.enable (mkDefault cfg.uri.web-rad.browser);
};
desktopEntries = let
handler = { 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;
};
in listToAttrs (map (v: {
name = "rad-to-${v.shortName}";
value = mkIf cfg.uri.rad.${v.shortName}.enable (handler v);
}) [
{
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}/";
}
]);
};
};
}

View file

@ -0,0 +1,158 @@
{ config, lib, pkgs, options, ... }:
let
inherit (lib)
generators getBin getExe getExe' mapAttrsToList mkDefault mkEnableOption
mkIf mkOption mkPackageOption;
inherit (lib.types) attrsOf nullOr oneOf package path str;
cfg = config.services.radicle;
opt = options.services.radicle;
radicleHome = config.home.homeDirectory + "/.radicle";
gitPath = [ "PATH=${getBin config.programs.git.package}/bin" ];
env = attrs:
(mapAttrsToList (generators.mkKeyValueDefault { } "=") attrs) ++ gitPath;
in {
meta.maintainers = with lib.maintainers; [ lorenzleutgeb ];
options = {
services.radicle = {
node = {
enable = mkEnableOption "Radicle Node";
package = mkPackageOption pkgs "radicle-node" { };
args = mkOption {
type = str;
default = "";
example = "--listen 0.0.0.0:8776";
};
environment = mkOption {
type = attrsOf (nullOr (oneOf [ str path package ]));
default = { };
};
};
httpd = {
enable = mkEnableOption "Radicle HTTP Daemon";
package = mkPackageOption pkgs "radicle-httpd" { };
args = mkOption {
type = str;
default = "--listen 127.0.0.1:8080";
};
environment = mkOption {
type = attrsOf (nullOr (oneOf [ str path package ]));
default = cfg.node.enable;
};
};
};
};
config = mkIf (cfg.node.enable || cfg.httpd.enable) {
assertions = [{
assertion = cfg.httpd.enable -> cfg.node.enable;
message = "`${opt.httpd.enable}` requires `${opt.node.enable}`, "
+ "since `radicle-httpd` depends on `radicle-node`";
}];
systemd.user = {
services = {
"radicle-keys" = {
Unit = {
Description = "Radicle Keys";
Documentation = [
"man:rad(1)"
"https://radicle.xyz/guides/user#come-into-being-from-the-elliptic-aether"
];
After = [ "default.target" ];
Requires = [ "default.target" ];
};
Service = {
Type = "oneshot";
Slice = "background.slice";
ExecStart = getExe (pkgs.writeShellApplication {
name = "radicle-keys.sh";
runtimeInputs = [ pkgs.coreutils ];
text = let
keyFile = name: "${radicleHome}/keys/${name}";
keyPair = name: [ (keyFile name) (keyFile (name + ".pub")) ];
radicleKeyPair = keyPair "radicle";
in ''
echo testing
FILES=(${builtins.concatStringsSep " " radicleKeyPair})
if stat --terse "''${FILES[@]}"
then
# Happy path, we're done!
exit 0
fi
cat <<EOM
At least one of the following files does not exist, but all should!
$(printf ' %s\n' "''${FILES[@]}")
In order for Radicle to work, please initialize by executing
rad auth
or provisioning pre-existing keys manually, e.g.
ln -s ~/.ssh/id_ed25519 ${keyFile "radicle"}
ln -s ~/.ssh/id_ed25519.pub ${keyFile "radicle.pub"}
EOM
exit 1
'';
});
};
};
"radicle-node" = mkIf cfg.node.enable {
Unit = {
Description = "Radicle Node";
After = [ "radicle-keys.service" ];
Requires = [ "radicle-keys.service" ];
Documentation =
[ "https://radicle.xyz/guides" "man:radicle-node(1)" ];
};
Service = {
Slice = "session.slice";
ExecStart =
"${getExe' cfg.node.package "radicle-node"} ${cfg.node.args}";
Environment = env cfg.node.environment;
KillMode = "process";
Restart = "always";
RestartSec = "2";
RestartSteps = "100";
RestartDelayMaxSec = "1min";
};
};
"radicle-httpd" = mkIf cfg.httpd.enable {
Unit = {
Description = "Radicle HTTP Daemon";
After = [ "radicle-node.service" ];
Requires = [ "radicle-node.service" ];
Documentation =
[ "https://radicle.xyz/guides" "man:radicle-httpd(1)" ];
};
Service = {
Slice = "session.slice";
ExecStart =
"${getExe' cfg.httpd.package "radicle-httpd"} ${cfg.httpd.args}";
Environment = env cfg.httpd.environment;
KillMode = "process";
Restart = "always";
RestartSec = "4";
RestartSteps = "100";
RestartDelayMaxSec = "2min";
};
};
};
sockets."radicle-node" = mkIf cfg.node.enable {
Unit = {
Description = "Radicle Node Control Socket";
Documentation = [ "man:radicle-node(1)" ];
};
Socket.ListenStream = "${radicleHome}/node/control.sock";
Install.WantedBy = [ "sockets.target" ];
};
};
programs.radicle.enable = mkDefault true;
};
}

View file

@ -262,6 +262,7 @@ in import nmtSrc {
./modules/services/picom
./modules/services/playerctld
./modules/services/polybar
./modules/services/radicle
./modules/services/recoll
./modules/services/redshift-gammastep
./modules/services/remmina

View file

@ -0,0 +1 @@
{}

View file

@ -0,0 +1,13 @@
{ config, pkgs, ... }:
{
config = {
services.radicle = { enable = true; };
nmt.script = ''
assertFileContent \
home-files/.radicle/config.json \
${./basic-configuration.json}
'';
};
}

View file

@ -0,0 +1 @@
{ radicle-basic-configuration = ./basic-configuration.nix; }