Radish alpha
r
rad:z371PVmDHdjJucejRoRYJcDEvD5pp
Radicle website including documentation and guides
Radicle
Git
Add a special css declaration for the faq footer
Archived lorenz opened 11 months ago

By taking the footer css declaration used for the global footer, we where hiding some of the text at the bottom of the faq page.

Update sebastinez signing key

chore: add nix flake

Adds a simple jekyll Nix flake.

To run the website use:

nix run .#bundle

Followed by:

make serve

A Legal Resource for Node Operators

Adds a legal resource outlining the legalities that node operators should be made aware of.

chore: change install verification key

Changes the verification key to use Fintan’s key instead of cloudhead’s.

Leaving a note to change this to use a more secure way of doing this at a later point.

WIP: install-test

chore: ignore .jj and .direnv

chore: add .vercelignore

guides: Stop recommending usage of port 8776

14 files changed +595 -70 092b4ce9 2ccdfa86
added .envrc
@@ -0,0 +1 @@
+
use flake
modified .gitignore
@@ -6,3 +6,5 @@ node_modules/
vendor/
notes/
.vercel
+
.jj
+
.direnv

\ No newline at end of file
added .vercelignore
@@ -0,0 +1,4 @@
+
flake.lock
+
LICENSE
+
.direnv
+
.jj

\ No newline at end of file
modified Gemfile.lock
@@ -82,51 +82,21 @@ GEM
    rexml (3.3.8)
    rouge (4.4.0)
    safe_yaml (1.0.5)
-
    sass-embedded (1.79.4)
-
      google-protobuf (~> 4.27)
+
    sass-embedded (1.83.0)
+
      google-protobuf (~> 4.28)
      rake (>= 13)
-
    sass-embedded (1.79.4-aarch64-linux-android)
-
      google-protobuf (~> 4.27)
-
    sass-embedded (1.79.4-aarch64-linux-gnu)
-
      google-protobuf (~> 4.27)
-
    sass-embedded (1.79.4-aarch64-linux-musl)
-
      google-protobuf (~> 4.27)
-
    sass-embedded (1.79.4-aarch64-mingw-ucrt)
-
      google-protobuf (~> 4.27)
-
    sass-embedded (1.79.4-arm-linux-androideabi)
-
      google-protobuf (~> 4.27)
-
    sass-embedded (1.79.4-arm-linux-gnueabihf)
-
      google-protobuf (~> 4.27)
-
    sass-embedded (1.79.4-arm-linux-musleabihf)
-
      google-protobuf (~> 4.27)
-
    sass-embedded (1.79.4-arm64-darwin)
-
      google-protobuf (~> 4.27)
-
    sass-embedded (1.79.4-riscv64-linux-android)
-
      google-protobuf (~> 4.27)
-
    sass-embedded (1.79.4-riscv64-linux-gnu)
-
      google-protobuf (~> 4.27)
-
    sass-embedded (1.79.4-riscv64-linux-musl)
-
      google-protobuf (~> 4.27)
-
    sass-embedded (1.79.4-x86-cygwin)
-
      google-protobuf (~> 4.27)
-
    sass-embedded (1.79.4-x86-linux-android)
-
      google-protobuf (~> 4.27)
-
    sass-embedded (1.79.4-x86-linux-gnu)
-
      google-protobuf (~> 4.27)
-
    sass-embedded (1.79.4-x86-linux-musl)
-
      google-protobuf (~> 4.27)
-
    sass-embedded (1.79.4-x86-mingw-ucrt)
-
      google-protobuf (~> 4.27)
-
    sass-embedded (1.79.4-x86_64-cygwin)
-
      google-protobuf (~> 4.27)
-
    sass-embedded (1.79.4-x86_64-darwin)
-
      google-protobuf (~> 4.27)
-
    sass-embedded (1.79.4-x86_64-linux-android)
-
      google-protobuf (~> 4.27)
-
    sass-embedded (1.79.4-x86_64-linux-gnu)
-
      google-protobuf (~> 4.27)
-
    sass-embedded (1.79.4-x86_64-linux-musl)
-
      google-protobuf (~> 4.27)
+
    sass-embedded (1.83.0-aarch64-mingw-ucrt)
+
      google-protobuf (~> 4.28)
+
    sass-embedded (1.83.0-arm64-darwin)
+
      google-protobuf (~> 4.28)
+
    sass-embedded (1.83.0-x86-cygwin)
+
      google-protobuf (~> 4.28)
+
    sass-embedded (1.83.0-x86-mingw-ucrt)
+
      google-protobuf (~> 4.28)
+
    sass-embedded (1.83.0-x86_64-cygwin)
+
      google-protobuf (~> 4.28)
+
    sass-embedded (1.83.0-x86_64-darwin)
+
      google-protobuf (~> 4.28)
    terminal-table (3.0.2)
      unicode-display_width (>= 1.1.1, < 3)
    unicode-display_width (2.6.0)
modified _guides/seeder.md
@@ -37,7 +37,13 @@ Therefore, a healthy peer-to-peer network necessitates at least *some* highly
available nodes that participate in the network like regular peers, but seldom
go offline. These are called seed nodes.

+
### Liability of running seed nodes
+

+
Please see our [legal] page for more information on potential legalities of
+
running a seed node.
+

[bittorrent]: https://en.wikipedia.org/wiki/BitTorrent
+
[legal]: /legal

Getting Started
---------------
@@ -164,8 +170,8 @@ is often set, such that all data on the network is stored and replicated.

Second, we must set an external address for the node to be reached on the
network. This address will be advertised to peers, allowing them to connect to
-
your seed node. Generally, this will be a DNS name with port `8776`, for
-
example `seed.radicle.example:8776`.
+
your seed node. Generally, this will be a DNS name with a port, for
+
example `seed.radicle.example:58776`.[^port]

Here's an example minimal configuration file with a permissive seeding policy
and external address set:
@@ -174,7 +180,7 @@ and external address set:
{
  "node": {
    "alias": "seed.radicle.example",
-
    "externalAddresses": ["seed.radicle.example:8776"],
+
    "externalAddresses": ["seed.radicle.example:58776"],
    "seedingPolicy": {
      "default": "allow",
      "scope": "all"
@@ -313,14 +319,14 @@ to 16 external addresses.

You'll find this setting in your configuration file, under
`node.externalAddresses`. External addresses are JSON strings of the form
-
`<host>:<port>`, for example `seed.radicle.example:8776`, where `<host>` is a
-
DNS name, and port is usually `8776`.
+
`<host>:<port>`, for example `seed.radicle.example:58776`, where `<host>` is a
+
DNS name, and `<port>` is a TCP port number.[^port]

```json
{
  "node": {
    ...
-
    "externalAddresses": ["seed.radicle.example:8776"]
+
    "externalAddresses": ["seed.radicle.example:58776"]
  }
}
```
@@ -433,8 +439,9 @@ have to run all commands via `sudo`, like we've been doing so far.

### Firewalls

-
If you are running a firewall, ensure that port `8776` is open for TCP
-
connections. This will allow inbound connections to your node.
+
If you are running a firewall, ensure that port the port Radicle node listens
+
on is open for TCP connections. This will allow inbound connections to your
+
node.

> It's recommended to run a basic firewall to further lock down your server,
> using something like `iptables`, though this is out of scope for this guide.
@@ -558,9 +565,13 @@ Come join us on our community chat and tell us about your seed node on the

🎊👾

+
[^port]: Previous versions of this guide suggested to use port `8776`.
+
         This recommendation was changed to comply with [RFC 6335, Sec. 6][ports].
+

[radicle-node]: https://seed.radicle.xyz/raw/rad:z3gqcJUoA1n9HaHKufZs5FCSGazv5/570a7eb141b6ba001713c46345d79b6fead1ca15/systemd/radicle-node.service
[caddy]: https://caddyserver.com/
[caddy-guide]: https://caddyserver.com/docs/running#linux-service
[caddy-install]: https://caddyserver.com/docs/install
[zulip]: https://radicle.zulipchat.com/#narrow/stream/384534-seeds
+
[ports]: https://datatracker.ietf.org/doc/html/rfc6335#autoid-8
[download]: /download
modified _guides/user.md
@@ -1705,7 +1705,7 @@ the case.
>    ```
>    For example:
>    ```
-
>    $ rad node connect z6MkqNZS9QWvC4wbZ8Vz4hQZ1FzN8q4XGj2KGmK9qNgQ8VWt@seed.darkstar.example:8776
+
>    $ rad node connect z6MkqNZS9QWvC4wbZ8Vz4hQZ1FzN8q4XGj2KGmK9qNgQ8VWt@seed.darkstar.example:58776
>    ```
>
> To find your seed node's address, run `rad node config --addresses` while logged in
@@ -1848,11 +1848,11 @@ Then we update our Tor configuration (`/etc/tor/torrc`) by adding the following
lines to it:

    HiddenServiceDir /var/lib/tor/radicle
-
    HiddenServicePort 8776
+
    HiddenServicePort 58776

These lines set the `HiddenServiceDir` to the path of the directory we just
-
created and specify the `HiddenServicePort` as `8776`, which is the default
-
Radicle node port.
+
created and specify the `HiddenServicePort` as `58776`, which is the same port
+
number that we will configure the Radicle node to listen on (see below). [^port]

Finally, we restart our Tor daemon to apply the configuration changes:

@@ -1927,14 +1927,15 @@ This tells Radicle that for `.onion` addresses, we want to use the specified
Tor *SOCKS5* proxy address. This is the address on which the Tor daemon should
be listening.

-
Next, we ensure our node is listening for incoming connections on port `8776`.
-
This is the port that our Tor proxy will be using to bridge connections to our
-
node, and is the same as the `HiddenServicePort` in our Tor configuration.
+
Next, we ensure our node is listening for incoming connections on the loopback
+
interface on port `58776`. This is the port that our Tor proxy will be using to
+
bridge connections to our node, and is the same as the `HiddenServicePort` in
+
our Tor configuration. [^port]

```json
"node": {
    ...
-
    "listen": ["0.0.0.0:8776"]
+
    "listen": ["127.0.0.1:58776"]
}
```

@@ -1946,7 +1947,7 @@ configure our node to advertise its `.onion` address to peers.
"node": {
    ...
    "externalAddresses": [
-
        "1odmmeotgcfx65l5hn6ejkaruvai222vs7o7tmtllszqk5xbysolfdd.onion:8776"
+
        "1odmmeotgcfx65l5hn6ejkaruvai222vs7o7tmtllszqk5xbysolfdd.onion:58776"
    ]
}
```
@@ -1973,7 +1974,7 @@ direct connection with us:

<aside class="kicker"><code>Calyx</code></aside>

-
    $ rad node connect z6Mkhp7VUnuufpvuQ3PdysShAjL86VDRUpPpkesqiysDBGs9@odmmeotgcfx65l5hn6ejkaruvai222vs7o7tmtllszqk5xbysolfdd.onion:8776
+
    $ rad node connect z6Mkhp7VUnuufpvuQ3PdysShAjL86VDRUpPpkesqiysDBGs9@odmmeotgcfx65l5hn6ejkaruvai222vs7o7tmtllszqk5xbysolfdd.onion:58776

Notice that the connect address is a combination of the peer's NID and its
`.onion` address:
@@ -1989,7 +1990,7 @@ address to the `connect` field in your node configuration:
"node": {
    ...
    "connect": [
-
        "z6Mkhp7VUnuufpvuQ3PdysShAjL86VDRUpPpkesqiysDBGs9@odmmeotgcfx65l5hn6ejkaruvai222vs7o7tmtllszqk5xbysolfdd.onion:8776"
+
        "z6Mkhp7VUnuufpvuQ3PdysShAjL86VDRUpPpkesqiysDBGs9@odmmeotgcfx65l5hn6ejkaruvai222vs7o7tmtllszqk5xbysolfdd.onion:58776"
    ]
}
```
@@ -2077,6 +2078,9 @@ to resolve `.onion` addresses.
> encounter any issues, we encourage you to report them to
> **security@radicle.xyz**.

+
[^port]: Previous versions of this guide suggested to use port `8776`.
+
         This recommendation was changed to comply with [RFC 6335, Sec. 6][ports].
+

[proto]: /guides/protocol/
[seeder]: /guides/seeder/
[zulip]: https://radicle.zulipchat.com/
@@ -2087,6 +2091,7 @@ to resolve `.onion` addresses.
[install-tor]: https://community.torproject.org/onion-services/setup/install/
[cyph-man]: https://www.activism.net/cypherpunk/manifesto.html
[heartwood]: https://app.radicle.xyz/nodes/seed.radicle.xyz/rad:z3gqcJUoA1n9HaHKufZs5FCSGazv5
+
[ports]: https://datatracker.ietf.org/doc/html/rfc6335#autoid-8
[cobs]: /guides/protocol/#collaborative-objects
[ch1]: /guides/user/#1-getting-started
[ch2]: /guides/user/#2-collaborating-the-radicle-way
modified _pages/download.md
@@ -127,8 +127,8 @@ reproduce the binaries on this page from source.

<table>
  <thead><th>Signer</th></thead>
-
  <tr><td>Key</td><td><code>ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIKU7IHRsae2q1/qd8NaWxfGhPEFGHwK1dcxvSjNdttjb</code></td></tr>
-
  <tr><td>Fingerprint</td><td><code>SHA256:mqjWN1YrPRDTcVTxB4IZPHyH+vXpjWSogi+3zezZ/rQ</code></td></tr>
+
  <tr><td>Key</td><td><code>ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIFw+wCPZUh8OndhgavJMSZluorVvzJjz22PH81XrkvIu</code></td></tr>
+
  <tr><td>Fingerprint</td><td><code>SHA256:HYV32eX6vSKEXpuXNstYGsbWDYTOxG1eqv/HjwuZnck</code></td></tr>
  <tr><td>Owner</td><td>me@sebastinez.dev</td></tr>
</table>

modified _pages/faq.md
@@ -277,9 +277,16 @@ The Radicle team is funded by [Radworks][radworks]. To date, around $7m has
been granted towards the development of Radicle, by Radworks. Grant proposals
for the last two years can be found [here][tally].

+
# Legal
+

+
## Am I liable as a Node Operator?
+

+
Please see our [legal] page for more information.
+

[tally]: https://www.tally.xyz/gov/radworks
[radworks]: https://radworks.org
[history]: /history
+
[legal]: /legal

<footer>
  <hr>
added _pages/legal.md
@@ -0,0 +1,52 @@
+
---
+
title: A Legal Resource for Node Operators
+
layout: page
+
---
+

+
# A Legal Resource for Node Operators
+

+
The Radicle project is committed to fostering a decentralized and
+
censorship-resistant internet. We believe in the power of distributed systems to
+
promote freedom of information and resist single points of control. Without
+
seeking to patronize or intending to introduce centralized content moderation
+
elements, we want to provide node operators with certain best practices that
+
seem to properly address illegal and harmful content while maintaining our core
+
belief in decentralization and censorship resistance. By doing so, we hope you
+
will be part of maintaining a safe and lawful Radicle ecosystem for all
+
participants.
+

+
## Applicable Laws and Regulations:
+
- Copyright Infringement: Respecting intellectual property rights and taking
+
  down content upon notification of copyright infringement in accordance with
+
  relevant laws.
+
- Child Sexual Abuse Material (CSAM): Proactively identifying and reporting any
+
  suspected instances of child sexual abuse material in compliance with the
+
  National Center for Missing & Exploited Children (NCMEC) guidelines and
+
  reporting procedures.
+
- Hate Speech and Discrimination: Avoiding the storage and distribution of
+
  content that promotes hate speech, discrimination, or violence against any
+
  individual or group, adhering to principles outlined by organizations like the
+
  Global Network Initiative (GNI). Terrorism and Extremism: Refusing to store or
+
  distribute content that promotes terrorism, extremism, or violence, aligning
+
  with best practices established by organizations like the Counter Extremism
+
Project.
+
- Data Privacy: Ensuring compliance with data privacy regulations such as the
+
  General Data Protection Regulation (GDPR) and the California Consumer Privacy
+
  Act (CCPA).
+

+
## Important Notes:
+
This resource is meant to inform node operators about some of the basic laws and
+
regulations that apply to content moderation; it is not an exhaustive list. Node
+
operators are responsible for ensuring their own compliance with all applicable
+
laws and regulations. This notice does not absolve node operators from any
+
liability arising from storing or distributing illegal or harmful content.
+

+
This resource may be updated from time to time to reflect changes in applicable
+
laws, regulations, and best practices, and to incorporate updates from relevant
+
standard-setting bodies.
+

+
This communication is for informational purposes only and does not constitute
+
legal advice. The information provided here is not intended to replace
+
professional legal counsel. We strongly recommend consulting with a qualified
+
legal professional for specific advice regarding your individual circumstances
+
and how these regulations may affect you.
modified assets/css/page.css
@@ -299,6 +299,11 @@ body > footer .radworks {
body > footer a {
  border-bottom: none;
}
+
main > footer {
+
  position: initial;
+
  padding-bottom: 1em;
+
}
+

:root[data-theme="light"] .radworks {
  opacity: 0.33;
}
added flake.lock
@@ -0,0 +1,104 @@
+
{
+
  "nodes": {
+
    "bundix": {
+
      "inputs": {
+
        "nixpkgs": [
+
          "nixpkgs"
+
        ]
+
      },
+
      "locked": {
+
        "lastModified": 1721093334,
+
        "narHash": "sha256-5FghZ0HIETbc0TGcBV8uyixq5z4w/9PF5Puhujz3D1o=",
+
        "owner": "inscapist",
+
        "repo": "bundix",
+
        "rev": "42c08846f7e5d91ef121239fd364feaeb22c0bbc",
+
        "type": "github"
+
      },
+
      "original": {
+
        "owner": "inscapist",
+
        "ref": "main",
+
        "repo": "bundix",
+
        "type": "github"
+
      }
+
    },
+
    "flake-utils": {
+
      "inputs": {
+
        "systems": "systems"
+
      },
+
      "locked": {
+
        "lastModified": 1731533236,
+
        "narHash": "sha256-l0KFg5HjrsfsO/JpG+r7fRrqm12kzFHyUHqHCVpMMbI=",
+
        "owner": "numtide",
+
        "repo": "flake-utils",
+
        "rev": "11707dc2f618dd54ca8739b309ec4fc024de578b",
+
        "type": "github"
+
      },
+
      "original": {
+
        "owner": "numtide",
+
        "repo": "flake-utils",
+
        "type": "github"
+
      }
+
    },
+
    "nixpkgs": {
+
      "locked": {
+
        "lastModified": 1735651292,
+
        "narHash": "sha256-YLbzcBtYo1/FEzFsB3AnM16qFc6fWPMIoOuSoDwvg9g=",
+
        "owner": "NixOS",
+
        "repo": "nixpkgs",
+
        "rev": "0da3c44a9460a26d2025ec3ed2ec60a895eb1114",
+
        "type": "github"
+
      },
+
      "original": {
+
        "owner": "NixOS",
+
        "ref": "release-24.05",
+
        "repo": "nixpkgs",
+
        "type": "github"
+
      }
+
    },
+
    "root": {
+
      "inputs": {
+
        "bundix": "bundix",
+
        "flake-utils": "flake-utils",
+
        "nixpkgs": "nixpkgs",
+
        "ruby-nix": "ruby-nix"
+
      }
+
    },
+
    "ruby-nix": {
+
      "inputs": {
+
        "nixpkgs": [
+
          "nixpkgs"
+
        ]
+
      },
+
      "locked": {
+
        "lastModified": 1744623277,
+
        "narHash": "sha256-0HWA2YD9v71SHyMF11PKnVJcHnrHhRLHDCldlUbzYII=",
+
        "owner": "inscapist",
+
        "repo": "ruby-nix",
+
        "rev": "43964ced23803f49e2d307fbbfd4e03ed23760c0",
+
        "type": "github"
+
      },
+
      "original": {
+
        "owner": "inscapist",
+
        "repo": "ruby-nix",
+
        "type": "github"
+
      }
+
    },
+
    "systems": {
+
      "locked": {
+
        "lastModified": 1681028828,
+
        "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=",
+
        "owner": "nix-systems",
+
        "repo": "default",
+
        "rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e",
+
        "type": "github"
+
      },
+
      "original": {
+
        "owner": "nix-systems",
+
        "repo": "default",
+
        "type": "github"
+
      }
+
    }
+
  },
+
  "root": "root",
+
  "version": 7
+
}
added flake.nix
@@ -0,0 +1,76 @@
+
{
+
  description = "radicle.xyz";
+

+
  inputs = {
+
    nixpkgs.url = "github:NixOS/nixpkgs/release-24.05";
+
    flake-utils.url = "github:numtide/flake-utils";
+
    bundix = {
+
      url = "github:inscapist/bundix/main";
+
      inputs.nixpkgs.follows = "nixpkgs";
+
    };
+
    ruby-nix = {
+
      url = "github:inscapist/ruby-nix";
+
      inputs.nixpkgs.follows = "nixpkgs";
+
    };
+
  };
+

+
  outputs = {
+
    self,
+
    nixpkgs,
+
    flake-utils,
+
    bundix,
+
    ruby-nix,
+
    ...
+
  }:
+
    flake-utils.lib.eachDefaultSystem (system: let
+
      pkgs = import nixpkgs {
+
        inherit system;
+
        overlays = [ ruby-nix.overlays.ruby ];
+
      };
+
      rubyNix = ruby-nix.lib pkgs;
+
		  bundixcli = bundix.packages.${system}.default;
+

+
      deps = with pkgs; [ env ruby bundixcli nodePackages.vercel ];
+

+
      inherit (rubyNix {
+
        name = "seroperson.gitlab.io";
+
        gemset = ./gemset.nix;
+
        gemConfig = pkgs.defaultGemConfig;
+
      })
+
        env ruby;
+
    in {
+
      packages = let
+
        bundlecli = pkgs.writeShellApplication {
+
          name = "bundle";
+
          runtimeInputs = deps;
+
          text = ''
+
          export BUNDLE_PATH=vendor/bundle
+
          bundle "$@"
+
        '';
+
        };
+
        jekyll = pkgs.writeShellApplication {
+
          name = "jekyll";
+
          runtimeInputs = deps;
+
          text = ''
+
          if [ $# -eq 0 ]; then
+
            jekyll build
+
          else
+
            jekyll "$@"
+
          fi
+
        '';
+
        };
+
      in {
+
        jekyll = jekyll;
+
        bundle = bundlecli;
+
        bundix = bundixcli;
+
        default = jekyll;
+
      };
+

+
      devShells.default = pkgs.mkShell {
+
        shellHook = ''
+
        export BUNDLE_PATH=vendor/bundle
+
      '';
+
        buildInputs = deps;
+
    };
+
  });
+
}
modified install
@@ -4,8 +4,10 @@
#
set -e

-
# SSH signing key for the release archives. This is currently cloudhead's key.
-
SIGNER="ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIL460KIEccS4881p7PPpiiQBsxF+H5tgC6De6crw9rbU"
+
# SSH signing key for the release archives. This currently cloudhead and fintan's keys.
+
# FIXME: Technically, there should be a release signing key, with a shamir threshold of > 1, with the secret encrypted by something like SOPS.
+
CLOUDHEAD="ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIL460KIEccS4881p7PPpiiQBsxF+H5tgC6De6crw9rbU"
+
FINTAN="ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIEFsaRqAJ1r6bBFwlcWzJKN7DdjItQDumCNc0wqw6Dvk"

url() {
  echo "https://files.radicle.xyz/releases/$1/radicle-$2.tar.xz"
@@ -124,11 +126,12 @@ verify() {
  archive="$1"
  signers="$(dirname $archive)/signers"
  # Add the signer key to the allowed signers file we pass to ssh-keygen.
-
  printf "cloudhead $SIGNER\n" > $signers
+
  printf "fintan@radicle.xyz $FINTAN\n" > $signers
+
  printf "cloudhead@radicle.xyz $CLOUDHEAD\n" >> $signers

  # Verify that `$archive` was signed by a key in `$signers`, identified by the
-
  # name "cloudhead", using the signature in `$archive.sig`.
-
  ssh-keygen -Y verify -f $signers -I cloudhead -n file -s "$archive.sig" < "$archive" || fatal "Invalid signature for $archive"
+
  # pattern "*@radicle.xyz", using the signature in `$archive.sig`.
+
  ssh-keygen -Y verify -f $signers -I "*@radicle.xyz" -n file -s "$archive.sig" < "$archive" || fatal "Invalid signature for $archive"
}

main() {
added install-test
@@ -0,0 +1,285 @@
+
#!/bin/sh
+
#
+
# Radicle installation script.
+
#
+
set -e
+

+
# SSH signing key for the release archives. This currently cloudhead and fintan's keys.
+
# FIXME: Technically, there should be a release signing key, with a shamir threshold of > 1, with the secret encrypted by something like SOPS.
+
CLOUDHEAD="ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIL460KIEccS4881p7PPpiiQBsxF+H5tgC6De6crw9rbU"
+
FINTAN="ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIEFsaRqAJ1r6bBFwlcWzJKN7DdjItQDumCNc0wqw6Dvk"
+

+
url() {
+
  echo "https://files.radicle.xyz/releases/$1/radicle-$2.tar.xz"
+
}
+

+
info() {
+
  printf "\033[36m$*\033[0m\n"
+
}
+

+
warn() {
+
  printf "\033[33m$*\033[0m\n"
+
}
+

+
rad_home() {
+
  if command -v rad >/dev/null 2>&1; then
+
    rad path || true
+
  fi
+
}
+

+
is_authed() {
+
  RAD_HOME=$(rad_home)
+

+
  if [ -n "$RAD_HOME" ] && [ -f "$RAD_HOME/keys/radicle.pub" ]; then
+
    return 0
+
  else
+
    return 1
+
  fi
+
}
+

+
success() {
+
  version="$1"
+

+
  printf "\033[32m✓\033[0m Radicle \033[2m$1\033[0m was installed successfully.\n"
+

+
  RAD_HOME=$(rad_home)
+

+
  if [ -n "$RAD_HOME" ] && [ -S "$RAD_HOME/node/control.sock" ];  then
+
    printf "\n"
+
    printf "Please restart your node to complete the upgrade.\n"
+
  fi
+
}
+

+
error() {
+
  printf "\033[31merror\033[0m: $*\n" >&2
+
}
+

+
fatal() {
+
  error "$@"
+
  exit 1
+
}
+

+
usage() {
+
  echo "Usage"
+
  echo
+
  echo "  $0 [<options>]"
+
  echo
+
  echo "  This script will install the Radicle binaries under '$RAD_PATH'".
+
  echo "  To change the location, set \$RAD_PATH to a different directory."
+
  echo
+
  echo "  Your Radicle home is set to '$RAD_HOME'."
+
  echo "  To change it, set \$RAD_HOME to a different directory."
+
  echo
+
  echo "Options"
+
  echo
+
  echo "  --no-modify-path    Do not modify the PATH environment variable"
+
  echo "  --version=VERSION   Install the given version of Radicle (default: latest)"
+
  echo "  --prefix=PATH       Radicle install prefix (default: ~/.radicle)"
+
  echo "  --help, -h          Show this help message and exit"
+
  echo
+
  echo "Environment"
+
  echo
+
  echo "  RAD_HOME            Radicle home directory"
+
}
+

+
target() {
+
  TARGET=""
+

+
  case "$(uname)/$(uname -m)" in
+
  Darwin/arm64)
+
    TARGET="aarch64-apple-darwin" ;;
+
  Darwin/x86_64)
+
    TARGET="x86_64-apple-darwin" ;;
+
  Linux/arm64|Linux/aarch64)
+
    TARGET="aarch64-unknown-linux-musl" ;;
+
  Linux/x86_64)
+
    TARGET="x86_64-unknown-linux-musl" ;;
+
  *)
+
    fatal "Your operating system is currently unsupported. Sorry!" ;;
+
  esac
+
  echo $TARGET
+
}
+

+
in_path() {
+
  IFS=":"
+

+
  for dir in $PATH; do
+
    if [ "$dir" = "$1" ]; then
+
      return 0 # The path is in $PATH
+
    fi
+
  done
+

+
  return 1 # The path is not in $PATH
+
}
+

+
get_started() {
+
  printf "\n"
+
  success "$@"
+

+
  if ! is_authed; then
+
    printf "\n"
+
    printf "Get started by creating your Radicle key pair with \033[35m\`rad auth\`\033[0m.\n"
+
  fi
+
}
+

+
verify() {
+
  archive="$1"
+
  signers="$(dirname $archive)/signers"
+
  # Add the signer key to the allowed signers file we pass to ssh-keygen.
+
  printf "fintan@radicle.xyz $FINTAN\n" > $signers
+
  printf "cloudhead@radicle.xyz $CLOUDHEAD\n" >> $signers
+

+
  # Verify that `$archive` was signed by a key in `$signers`, identified by the
+
  # pattern "*@radicle.xyz", using the signature in `$archive.sig`.
+
  ssh-keygen -Y verify -f $signers -I "*@radicle.xyz" -n file -s "$archive.sig" < "$archive" || fatal "Invalid signature for $archive"
+
}
+

+
main() {
+
  PREFIX=${RAD_HOME:-"$HOME/.radicle"}
+
  SHELL_PATH=${SHELL:-"/bin/sh"}
+
  NO_MODIFY_PATH=false
+
  VERSION=latest
+

+
  if [ -n "$RAD_PATH" ]; then
+
    fatal "RAD_PATH is no longer supported; Use '--prefix' instead"
+
  fi
+

+
  if ! command -v tar >/dev/null 2>&1; then
+
    fatal "The 'tar' tool is required to extract the archive; please install 'tar' and try again"
+
  fi
+

+
  if ! command -v xz >/dev/null 2>&1; then
+
    fatal "The 'xz' tool is required to extract the archive; please install 'xz' or 'xz-utils' and try again"
+
  fi
+

+
  if ! command -v curl >/dev/null 2>&1; then
+
    fatal "The 'curl' tool is required to download the archive; please install 'curl' and try again"
+
  fi
+

+
  if ! command -v ssh-keygen >/dev/null 2>&1; then
+
    fatal "The 'ssh-keygen' tool is required to verify the archive; please install the 'openssh' and try again"
+
  fi
+

+
  while :; do
+
    case "$1" in
+
      --no-modify-path)
+
        NO_MODIFY_PATH=true
+
        ;;
+
      --prefix=*)
+
        PREFIX=${1#*=}
+
        ;;
+
      --version=*)
+
        VERSION=${1#*=}
+
        ;;
+
      -h|--help)
+
        usage
+
        exit 0 ;;
+
      -*)
+
        error "Unrecognized argument '$1'"
+
        echo ; usage ; exit 1 ;;
+
      *)
+
        break ;;
+
    esac
+
    shift
+
  done
+

+
  if [ -z "$VERSION" ]; then
+
    fatal "Empty version string; use --version=VERSION"
+
  fi
+

+
  if [ -z "$PREFIX" ]; then
+
    fatal "Empty installation prefix; use --prefix=PATH or set RAD_HOME"
+
  fi
+
  mkdir -p $PREFIX
+

+
  echo
+
  echo "👾 Welcome to Radicle"
+
  echo
+

+
  # Where to install binaries.
+
  RAD_PATH=$PREFIX/bin
+

+
  info "Detecting operating system..."
+
  TARGET=$(target)
+
  ARCHIVE="$(mktemp -d)/radicle-$TARGET.tar.xz" # Nb. `-d` must be used on BSDs.
+
  URL="$(url "$VERSION" "$TARGET")"
+

+
  info "Downloading $URL..."
+
  curl --proto '=https' --fail --tlsv1.2 -# -L "$URL"     -o "$ARCHIVE"     || fatal "Failed to fetch $URL" # Nb. `--tlsv1.3` is not supported on macOS
+
  info "Downloading $URL.sig..."
+
  curl --proto '=https' --fail --tlsv1.2 -# -L "$URL.sig" -o "$ARCHIVE.sig" || fatal "Failed to fetch $URL.sig"
+

+
  info "Verifying $(basename $ARCHIVE)..."
+
  verify "$ARCHIVE"
+

+
  info "Installing Radicle into $PREFIX..."
+
  tar -xJf "$ARCHIVE" --strip-components=1 -C "$PREFIX"
+

+
  chmod +x \
+
    $RAD_PATH/radicle-node \
+
    $RAD_PATH/rad \
+
    $RAD_PATH/git-remote-rad
+

+
  if ! command -v git >/dev/null 2>&1; then
+
    warn
+
    warn "Warning: a Git installation was not detected on your system."
+
    warn "Warning: please install Git for Radicle to work correctly."
+
  fi
+
  version="$($RAD_PATH/rad --version | cut -f2 -d' ' -)"
+

+
  # If radicle is not in $PATH, add it here.
+
  if $NO_MODIFY_PATH; then
+
    info "Not modifying shell path variable, as requested."
+
    get_started $version
+
  elif in_path $RAD_PATH; then
+
    info "Radicle is configured in shell already, not modifying."
+
    get_started $version
+
  else
+
    PROFILE=""
+

+
    case $SHELL_PATH in
+
      */zsh)
+
        PROFILE=$HOME/.zshenv ;;
+
      */bash)
+
        PROFILE=$HOME/.bashrc ;;
+
      */fish)
+
        PROFILE=$HOME/.config/fish/config.fish ;;
+
      */ash)
+
        PROFILE=$HOME/.profile ;;
+
      */csh)
+
        PROFILE=$HOME/.cshrc ;;
+
    esac
+

+
    if [ -z "$PROFILE" ]; then
+
      warn "Warning: unable to update your PATH variable."
+
      warn "Warning: please manually add $RAD_PATH to your PATH."
+
      get_started $version
+
    else
+
      info "Configuring path variable in ~${PROFILE#$HOME}..."
+
      echo                                    >> "$PROFILE"
+
      echo "# Added by Radicle."              >> "$PROFILE"
+
      echo "export PATH=\"\$PATH:$RAD_PATH\"" >> "$PROFILE"
+
      echo
+

+
      success $version
+

+
      if command -v rad >/dev/null 2>&1; then
+
        EXISTING=$(command -v rad)
+
        printf "\n"
+
        warn "Warning: Pre-existing binaries found at $(dirname "$EXISTING")."
+
        warn "Warning: Installed new binaries in $RAD_PATH."
+
      fi
+

+
      printf "\n"
+
      printf "Before running Radicle for the first time,\n"
+
      printf "run \033[34m\`source ~${PROFILE#$HOME}\`\033[0m or open a new terminal.\n"
+

+
      if ! is_authed; then
+
        printf "\n"
+
        printf "Then, create your Radicle key pair with \033[35m\`rad auth\`\033[0m.\n"
+
      fi
+
    fi
+
  fi
+
}
+

+
main "$@"