From 325c2571b79bc7b6e358b223b2c058f62a4c78f5 Mon Sep 17 00:00:00 2001 From: Eric Li Date: Tue, 29 Sep 2026 22:16:14 -0400 Subject: [PATCH 1/3] feat: add one-line install script --- .github/workflows/installer.yml | 97 ++++++++++++++ .github/workflows/release.yml | 12 ++ README.md | 25 +++- docs/guides/getting-started.mdx | 20 ++- example/nylon.service | 13 +- install.sh | 226 ++++++++++++++++++++++++++++++++ 6 files changed, 387 insertions(+), 6 deletions(-) create mode 100644 .github/workflows/installer.yml create mode 100755 install.sh diff --git a/.github/workflows/installer.yml b/.github/workflows/installer.yml new file mode 100644 index 00000000..b91a4579 --- /dev/null +++ b/.github/workflows/installer.yml @@ -0,0 +1,97 @@ +name: Installer + +on: + push: + branches: [main] + paths: + - "install.sh" + - ".github/workflows/installer.yml" + pull_request: + branches: [main] + paths: + - "install.sh" + - ".github/workflows/installer.yml" + # Also catch new releases that break the installer. + schedule: + - cron: "0 6 * * 1" + workflow_dispatch: + +permissions: + contents: read + +jobs: + shellcheck: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - name: Run shellcheck + run: shellcheck install.sh + + # Installs published releases on a real Ubuntu 24.04 VM with systemd, as a + # non-root user with sudo, the way most people will run it. + ubuntu: + runs-on: ubuntu-24.04 + steps: + - uses: actions/checkout@v4 + - name: Install binary + run: | + sh install.sh + nylon version + - name: Re-run is a no-op + run: | + version=$(nylon version | sed 's/^Version: //') + sh install.sh 2>&1 | grep -Fx "==> nylon $version is already installed" + - name: Install service without configs + run: | + # A pre-existing config dir must still end up root-only. + sudo mkdir -m 0755 /etc/nylon + sh install.sh --service + systemctl is-enabled nylon + test "$(systemctl is-active nylon)" = inactive + test "$(sudo stat -c %a /etc/nylon)" = 700 + - name: Start service with a single-node config + run: | + key=$(nylon key 2>pub) + printf 'id: ci\nkey: %s\nport: 57175\n' "$key" | sudo tee /etc/nylon/node.yaml >/dev/null + printf 'routers:\n - id: ci\n pubkey: %s\n addresses: [10.99.0.1]\n' "$(cat pub)" | sudo tee /etc/nylon/central.yaml >/dev/null + sudo nylon verify /etc/nylon/central.yaml --node /etc/nylon/node.yaml + sh install.sh --service 2>&1 | grep -Fx "==> started nylon.service" + # Same PID after a while, so it isn't crash-looping. + sleep 2 + pid=$(systemctl show -p MainPID --value nylon) + sleep 10 + test "$pid" != 0 + test "$(systemctl show -p MainPID --value nylon)" = "$pid" + ip -br addr | grep 10.99.0.1 + - name: Re-running with --service leaves it running + run: | + pid=$(systemctl show -p MainPID --value nylon) + sh install.sh --service 2>&1 | grep -Fx "==> nylon.service is already running" + test "$(systemctl show -p MainPID --value nylon)" = "$pid" + - name: Changing version restarts the service + run: | + sh install.sh --version v0.4.5 2>&1 | grep -Fx "==> restarted nylon.service" + nylon version | grep -Fx "Version: v0.4.5" + sh install.sh 2>&1 | grep -Fx "==> restarted nylon.service" + sleep 2 + pid=$(systemctl show -p MainPID --value nylon) + sleep 10 + test "$pid" != 0 + test "$(systemctl show -p MainPID --value nylon)" = "$pid" + - name: Uninstall + run: | + sh install.sh --uninstall + test "$(systemctl is-active nylon)" != active + if ip link show nylon 2>/dev/null; then exit 1; fi + test ! -e /usr/local/bin/nylon + test ! -e /etc/systemd/system/nylon.service + sudo test -f /etc/nylon/node.yaml + - name: Leaves a hand-written unit alone + run: | + printf '[Service]\nExecStart=/bin/true\n' | sudo tee /etc/systemd/system/nylon.service >/dev/null + if sh install.sh --service; then exit 1; fi + sh install.sh --uninstall + sudo test -f /etc/systemd/system/nylon.service + - name: Logs + if: failure() + run: sudo journalctl -u nylon --no-pager diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index cedf3f93..544f376d 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -30,6 +30,7 @@ jobs: goos: ${{ matrix.goos }} goarch: ${{ matrix.goarch }} asset_name: nylon-${{ matrix.goos }}-${{ matrix.goarch }} + sha256sum: TRUE release-windows: name: Release Go Binary (Windows) # Windows requires the wintun driver, so we use a separate action runs-on: ubuntu-latest @@ -53,3 +54,14 @@ jobs: goarch: ${{ matrix.goarch }} extra_files: 'wintun/bin/${{ matrix.goarch }}/wintun.dll' asset_name: nylon-${{ matrix.goos }}-${{ matrix.goarch }} + sha256sum: TRUE + release-installer: + name: Attach install.sh + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - name: Upload install.sh + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + TAG: ${{ github.event.release.tag_name }} + run: gh release upload "$TAG" install.sh --clobber diff --git a/README.md b/README.md index 19c8df60..bc76c965 100644 --- a/README.md +++ b/README.md @@ -20,7 +20,30 @@ Nylon targets under 10 seconds of convergence time after a link failure, as you ## Getting Started -Download the latest release binary from the [releases page](https://github.com/encodeous/nylon/releases), then head to the [docs](https://nylon.jq.ax) for setup instructions. +Install the latest release on Linux: + +```bash +curl -fsSL https://raw.githubusercontent.com/encodeous/nylon/main/install.sh | sh +``` + +To also run nylon as a systemd service, with its configs in `/etc/nylon`: + +```bash +curl -fsSL https://raw.githubusercontent.com/encodeous/nylon/main/install.sh | sh -s -- --service +``` + +Pass options after `sh -s --`, for example `... | sh -s -- --version v0.4.6`: + +| Option | Description | +|---|---| +| `--service` | Also install and enable a systemd service using the configs in `/etc/nylon` | +| `--version ` | Install a specific release, e.g. `v0.4.6` (also works for downgrades) | +| `--uninstall` | Remove the service and binary; keeps `/etc/nylon` | +| `--help` | Show usage | + +You can also download binaries directly from the [releases page](https://github.com/encodeous/nylon/releases). + +Then head to the [docs](https://nylon.jq.ax) for setup instructions. > **[Read the full documentation at nylon.jq.ax](https://nylon.jq.ax)** > includes configuration reference, guides for connecting WireGuard clients, port forwarding, and comparisons with Tailscale/Nebula. diff --git a/docs/guides/getting-started.mdx b/docs/guides/getting-started.mdx index 625eba5c..7edfee98 100644 --- a/docs/guides/getting-started.mdx +++ b/docs/guides/getting-started.mdx @@ -17,7 +17,13 @@ graph LR ## Prerequisites - Two machines with UDP port `57175` open (can configure with a different port). -- The `nylon` binary downloaded on both machines from the [releases page](https://github.com/encodeous/nylon/releases). +- The `nylon` binary installed on both machines. On Linux: + + ```bash + curl -fsSL https://raw.githubusercontent.com/encodeous/nylon/main/install.sh | sh + ``` + + On other platforms, download it from the [releases page](https://github.com/encodeous/nylon/releases). :::note The Linux and macOS versions are well tested, but the Windows TUN interface has known stability issues. For Windows, we recommend using [WireGuard for Windows](https://www.wireguard.com/install/) and connecting to a Linux/macOS machine as a [passive client](/guides/wg-clients). @@ -96,6 +102,18 @@ The Linux and macOS versions are well tested, but the Windows TUN interface has After a few seconds, the nodes will discover each other and establish a secure tunnel. You should be able to ping `10.0.0.2` from `node-1`. + :::tip[Running as a service] + On Linux with systemd, the installer can run nylon as a service instead. Stop the nylon you started above (Ctrl+C), then run: + + ```bash + curl -fsSL https://raw.githubusercontent.com/encodeous/nylon/main/install.sh | sh -s -- --service + sudo mv node.yaml central.yaml /etc/nylon/ + sudo systemctl start nylon + ``` + + The service reads its configs from `/etc/nylon` (readable only by root) and starts on boot. Follow its logs with `journalctl -u nylon -f`. Re-running the installer upgrades nylon and restarts the service if it's running. + ::: + ## Next Steps diff --git a/example/nylon.service b/example/nylon.service index d0cbd1cf..ecabf6ec 100644 --- a/example/nylon.service +++ b/example/nylon.service @@ -1,10 +1,15 @@ +# Sample unit; install.sh --service installs an equivalent one. [Unit] -Description=Nylon Service +Description=nylon mesh network +Documentation=https://nylon.jq.ax After=network-online.target +Wants=network-online.target [Service] -ExecStart=/usr/bin/nylon run -WorkingDirectory=/home/encodeous +ExecStart=/usr/local/bin/nylon run -c /etc/nylon/central.yaml -n /etc/nylon/node.yaml +WorkingDirectory=/etc/nylon +Restart=on-failure +RestartSec=5 [Install] -WantedBy=multi-user.target \ No newline at end of file +WantedBy=multi-user.target diff --git a/install.sh b/install.sh new file mode 100755 index 00000000..65d1a62d --- /dev/null +++ b/install.sh @@ -0,0 +1,226 @@ +#!/bin/sh +# nylon installer +# Installs the latest nylon release to /usr/local/bin. With --service, also +# installs a systemd unit that runs nylon with the configs in /etc/nylon. +# Re-running upgrades nylon and restarts the service. Tested on Ubuntu 24.04. + +set -eu + +REPO="encodeous/nylon" +BIN="/usr/local/bin/nylon" +CONFIG_DIR="/etc/nylon" +UNIT="/etc/systemd/system/nylon.service" + +usage() { + cat <] [--service] [--uninstall] + + --version Install a specific release, e.g. v0.4.6 (default: latest) + --service Also install and enable a systemd service that runs nylon + with the configs in $CONFIG_DIR + --uninstall Remove the service and binary (keeps $CONFIG_DIR) +EOF +} + +info() { echo "==> $*" >&2; } +die() { + echo "error: $*" >&2 + exit 1 +} + +as_root() { + if [ "$(id -u)" = 0 ]; then + "$@" + elif command -v sudo >/dev/null 2>&1; then + sudo "$@" + else + die "need root to run: $*" + fi +} + +detect_arch() { + case "$(uname -m)" in + x86_64 | amd64) ARCH=amd64 ;; + aarch64 | arm64) ARCH=arm64 ;; + i386 | i686) ARCH=386 ;; + *) die "unsupported architecture: $(uname -m)" ;; + esac +} + +# resolves latest to a tag +resolve_version() { + case "$VERSION" in + latest) + VERSION="$(curl -fsSLI -o /dev/null -w '%{url_effective}' "https://github.com/$REPO/releases/latest" | sed -n 's#.*/tag/##p')" + [ -n "$VERSION" ] || die "could not determine the latest release" + ;; + v*) ;; + *) VERSION="v$VERSION" ;; + esac +} + +installed_version() { + if [ -x "$BIN" ]; then + "$BIN" version 2>/dev/null | sed -n 's/^Version: //p' + fi +} + +download_and_install() { + asset="nylon-linux-$ARCH.tar.gz" + base="https://github.com/$REPO/releases/download/$VERSION" + + info "downloading $asset ($VERSION)" + curl -fsSL -o "$TMP/$asset" "$base/$asset" || die "failed to download $base/$asset" + + # check md5 for releases predating this pr (they didn't publish sha256) + if curl -fsSL -o "$TMP/sum" "$base/$asset.sha256" 2>/dev/null; then + actual="$(sha256sum "$TMP/$asset" | cut -d' ' -f1)" + elif curl -fsSL -o "$TMP/sum" "$base/$asset.md5" 2>/dev/null; then + actual="$(md5sum "$TMP/$asset" | cut -d' ' -f1)" + else + die "no checksum published for $asset; refusing to install" + fi + [ "$(cut -d' ' -f1 <"$TMP/sum")" = "$actual" ] || die "checksum mismatch for $asset" + + tar -xzf "$TMP/$asset" -C "$TMP" nylon + "$TMP/nylon" version >/dev/null 2>&1 || + die "the downloaded binary does not run on this system (if $(dirname "$TMP") is mounted noexec, set TMPDIR to another directory)" + + # atomic replace + as_root install -m 0755 "$TMP/nylon" "$BIN.new" + as_root mv -f "$BIN.new" "$BIN" + info "installed $BIN ($VERSION)" +} + +MARKER="# Managed by nylon's install.sh" + +service_running() { + systemctl is-active --quiet nylon +} + +# unit is only managed if it was installed by this script +managed_unit() { + grep -qF "$MARKER" "$UNIT" 2>/dev/null +} + +install_service() { + cat >"$TMP/unit" < Date: Tue, 29 Sep 2026 22:23:10 -0400 Subject: [PATCH 2/3] feat(install): install the systemd service by default --- .github/workflows/installer.yml | 19 ++++++++++--------- README.md | 10 ++-------- docs/guides/getting-started.mdx | 3 +-- install.sh | 20 +++++++++++--------- 4 files changed, 24 insertions(+), 28 deletions(-) diff --git a/.github/workflows/installer.yml b/.github/workflows/installer.yml index b91a4579..17d6a589 100644 --- a/.github/workflows/installer.yml +++ b/.github/workflows/installer.yml @@ -33,19 +33,20 @@ jobs: runs-on: ubuntu-24.04 steps: - uses: actions/checkout@v4 - - name: Install binary + - name: Install binary only run: | - sh install.sh + sh install.sh --no-service nylon version + test ! -e /etc/systemd/system/nylon.service - name: Re-run is a no-op run: | version=$(nylon version | sed 's/^Version: //') - sh install.sh 2>&1 | grep -Fx "==> nylon $version is already installed" - - name: Install service without configs + sh install.sh --no-service 2>&1 | grep -Fx "==> nylon $version is already installed" + - name: Install service (default) without configs run: | # A pre-existing config dir must still end up root-only. sudo mkdir -m 0755 /etc/nylon - sh install.sh --service + sh install.sh systemctl is-enabled nylon test "$(systemctl is-active nylon)" = inactive test "$(sudo stat -c %a /etc/nylon)" = 700 @@ -55,7 +56,7 @@ jobs: printf 'id: ci\nkey: %s\nport: 57175\n' "$key" | sudo tee /etc/nylon/node.yaml >/dev/null printf 'routers:\n - id: ci\n pubkey: %s\n addresses: [10.99.0.1]\n' "$(cat pub)" | sudo tee /etc/nylon/central.yaml >/dev/null sudo nylon verify /etc/nylon/central.yaml --node /etc/nylon/node.yaml - sh install.sh --service 2>&1 | grep -Fx "==> started nylon.service" + sh install.sh 2>&1 | grep -Fx "==> started nylon.service" # Same PID after a while, so it isn't crash-looping. sleep 2 pid=$(systemctl show -p MainPID --value nylon) @@ -63,10 +64,10 @@ jobs: test "$pid" != 0 test "$(systemctl show -p MainPID --value nylon)" = "$pid" ip -br addr | grep 10.99.0.1 - - name: Re-running with --service leaves it running + - name: Re-running leaves it running run: | pid=$(systemctl show -p MainPID --value nylon) - sh install.sh --service 2>&1 | grep -Fx "==> nylon.service is already running" + sh install.sh 2>&1 | grep -Fx "==> nylon.service is already running" test "$(systemctl show -p MainPID --value nylon)" = "$pid" - name: Changing version restarts the service run: | @@ -89,7 +90,7 @@ jobs: - name: Leaves a hand-written unit alone run: | printf '[Service]\nExecStart=/bin/true\n' | sudo tee /etc/systemd/system/nylon.service >/dev/null - if sh install.sh --service; then exit 1; fi + if sh install.sh; then exit 1; fi sh install.sh --uninstall sudo test -f /etc/systemd/system/nylon.service - name: Logs diff --git a/README.md b/README.md index bc76c965..633e9089 100644 --- a/README.md +++ b/README.md @@ -20,23 +20,17 @@ Nylon targets under 10 seconds of convergence time after a link failure, as you ## Getting Started -Install the latest release on Linux: +Install the latest release on Linux, along with a systemd service that runs it with the configs in `/etc/nylon`: ```bash curl -fsSL https://raw.githubusercontent.com/encodeous/nylon/main/install.sh | sh ``` -To also run nylon as a systemd service, with its configs in `/etc/nylon`: - -```bash -curl -fsSL https://raw.githubusercontent.com/encodeous/nylon/main/install.sh | sh -s -- --service -``` - Pass options after `sh -s --`, for example `... | sh -s -- --version v0.4.6`: | Option | Description | |---|---| -| `--service` | Also install and enable a systemd service using the configs in `/etc/nylon` | +| `--no-service` | Install only the binary without the systemd service | | `--version ` | Install a specific release, e.g. `v0.4.6` (also works for downgrades) | | `--uninstall` | Remove the service and binary; keeps `/etc/nylon` | | `--help` | Show usage | diff --git a/docs/guides/getting-started.mdx b/docs/guides/getting-started.mdx index 7edfee98..3edb4314 100644 --- a/docs/guides/getting-started.mdx +++ b/docs/guides/getting-started.mdx @@ -103,10 +103,9 @@ The Linux and macOS versions are well tested, but the Windows TUN interface has After a few seconds, the nodes will discover each other and establish a secure tunnel. You should be able to ping `10.0.0.2` from `node-1`. :::tip[Running as a service] - On Linux with systemd, the installer can run nylon as a service instead. Stop the nylon you started above (Ctrl+C), then run: + On Linux with systemd, the installer also set nylon up as a service. To run it that way instead, stop the nylon you started above (Ctrl+C), then run: ```bash - curl -fsSL https://raw.githubusercontent.com/encodeous/nylon/main/install.sh | sh -s -- --service sudo mv node.yaml central.yaml /etc/nylon/ sudo systemctl start nylon ``` diff --git a/install.sh b/install.sh index 65d1a62d..91335e9b 100755 --- a/install.sh +++ b/install.sh @@ -1,7 +1,7 @@ #!/bin/sh # nylon installer -# Installs the latest nylon release to /usr/local/bin. With --service, also -# installs a systemd unit that runs nylon with the configs in /etc/nylon. +# Installs the latest nylon release to /usr/local/bin as well as a systemd +# unit to enable nylon as a service. opt out with --no-service # Re-running upgrades nylon and restarts the service. Tested on Ubuntu 24.04. set -eu @@ -13,11 +13,11 @@ UNIT="/etc/systemd/system/nylon.service" usage() { cat <] [--service] [--uninstall] +Usage: install.sh [--version ] [--no-service] [--uninstall] --version Install a specific release, e.g. v0.4.6 (default: latest) - --service Also install and enable a systemd service that runs nylon - with the configs in $CONFIG_DIR + --no-service Don't install the systemd service that runs nylon with the + configs in $CONFIG_DIR (installed by default) --uninstall Remove the service and binary (keeps $CONFIG_DIR) EOF } @@ -166,7 +166,7 @@ uninstall() { main() { VERSION=latest - SERVICE=0 + SERVICE=1 UNINSTALL=0 while [ $# -gt 0 ]; do case "$1" in @@ -176,6 +176,7 @@ main() { shift ;; --service) SERVICE=1 ;; + --no-service) SERVICE=0 ;; --uninstall) UNINSTALL=1 ;; -h | --help) usage @@ -192,10 +193,11 @@ main() { exit 0 fi if [ "$SERVICE" = 1 ] && [ ! -d /run/systemd/system ]; then - die "--service needs systemd" + info "systemd isn't running; installing the binary only" + SERVICE=0 fi if [ "$SERVICE" = 1 ] && [ -f "$UNIT" ] && ! managed_unit; then - die "$UNIT exists but wasn't installed by this script; move it aside and re-run" + die "$UNIT exists but wasn't installed by this script; move it aside, or re-run with --no-service" fi detect_arch @@ -218,7 +220,7 @@ main() { as_root systemctl restart nylon info "restarted nylon.service" elif [ "$UPGRADED" = 1 ] && [ ! -f "$UNIT" ]; then - info "next: https://nylon.jq.ax/guides/getting-started (or re-run with --service)" + info "next: https://nylon.jq.ax/guides/getting-started" fi } From ffc261aa32a8c3c8a8154a99d2029e0552a2bf58 Mon Sep 17 00:00:00 2001 From: Adam <47889571+encodeous@users.noreply.github.com> Date: Wed, 30 Sep 2026 10:32:41 -0400 Subject: [PATCH 3/3] Trim readme --- README.md | 9 --------- 1 file changed, 9 deletions(-) diff --git a/README.md b/README.md index 633e9089..f7bcfc2a 100644 --- a/README.md +++ b/README.md @@ -26,15 +26,6 @@ Install the latest release on Linux, along with a systemd service that runs it w curl -fsSL https://raw.githubusercontent.com/encodeous/nylon/main/install.sh | sh ``` -Pass options after `sh -s --`, for example `... | sh -s -- --version v0.4.6`: - -| Option | Description | -|---|---| -| `--no-service` | Install only the binary without the systemd service | -| `--version ` | Install a specific release, e.g. `v0.4.6` (also works for downgrades) | -| `--uninstall` | Remove the service and binary; keeps `/etc/nylon` | -| `--help` | Show usage | - You can also download binaries directly from the [releases page](https://github.com/encodeous/nylon/releases). Then head to the [docs](https://nylon.jq.ax) for setup instructions.