From 18b4ce1a4007b734e5140b1ec36a4123aa5365a4 Mon Sep 17 00:00:00 2001 From: Harivansh Rathi Date: Wed, 1 Apr 2026 20:37:40 -0400 Subject: [PATCH] Add install script, CI workflows, and release pipeline - Install script: curl-pipe-sh installer that downloads the right binary for the user's OS/arch from GitHub Releases - CI workflow: runs go vet + go test for both Go modules and builds the web app on push/PR - Release workflow: goreleaser builds cross-platform binaries (linux/darwin, amd64/arm64) on version tags - Node-agent defaults BETTERNAS_CONTROL_PLANE_URL to https://api.betternas.com so users only need username/password --- .github/workflows/ci.yaml | 47 +++++++++++++ .github/workflows/release.yaml | 27 +++++++ apps/node-agent/README.md | 19 +++++ .../cmd/node-agent/control_plane.go | 6 +- apps/web/app/page.tsx | 17 +++-- scripts/install-betternas-node.sh | 70 +++++++++++++++++++ 6 files changed, 178 insertions(+), 8 deletions(-) create mode 100644 .github/workflows/ci.yaml create mode 100644 .github/workflows/release.yaml create mode 100755 scripts/install-betternas-node.sh diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml new file mode 100644 index 0000000..9846b53 --- /dev/null +++ b/.github/workflows/ci.yaml @@ -0,0 +1,47 @@ +name: CI + +on: + push: + branches: [main] + pull_request: + +jobs: + test-control-plane: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-go@v5 + with: + go-version-file: apps/control-plane/go.mod + cache-dependency-path: apps/control-plane/go.sum + - run: go vet ./... + working-directory: apps/control-plane + - run: go test -count=1 ./... + working-directory: apps/control-plane + + test-node-agent: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-go@v5 + with: + go-version-file: apps/node-agent/go.mod + cache-dependency-path: apps/node-agent/go.sum + - run: go vet ./... + working-directory: apps/node-agent + - run: go test -count=1 ./... + working-directory: apps/node-agent + + build-web: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: pnpm/action-setup@v4 + - uses: actions/setup-node@v4 + with: + node-version: 22 + cache: pnpm + - run: pnpm install --frozen-lockfile + - run: pnpm --filter @betternas/web build + env: + NEXT_PUBLIC_BETTERNAS_API_URL: https://api.betternas.com diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml new file mode 100644 index 0000000..58f6d02 --- /dev/null +++ b/.github/workflows/release.yaml @@ -0,0 +1,27 @@ +name: Release + +on: + push: + tags: + - "v*" + +permissions: + contents: write + +jobs: + release: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 0 + - uses: actions/setup-go@v5 + with: + go-version-file: apps/node-agent/go.mod + cache-dependency-path: apps/node-agent/go.sum + - uses: goreleaser/goreleaser-action@v6 + with: + version: latest + args: release --clean + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/apps/node-agent/README.md b/apps/node-agent/README.md index 1505dc0..46a3fee 100644 --- a/apps/node-agent/README.md +++ b/apps/node-agent/README.md @@ -15,3 +15,22 @@ For the scaffold it does two things: This is the first real storage-facing surface in the monorepo. The user-facing binary should be distributed as `betternas-node`. + +Install the latest release with: + +```bash +curl -fsSL https://raw.githubusercontent.com/harivansh-afk/betterNAS/main/scripts/install-betternas-node.sh | sh +``` + +Then connect a machine to betterNAS with: + +```bash +BETTERNAS_USERNAME=your-username \ +BETTERNAS_PASSWORD=your-password \ +BETTERNAS_EXPORT_PATH=/path/to/export \ +BETTERNAS_NODE_DIRECT_ADDRESS=https://your-public-node-url \ +betternas-node +``` + +If `BETTERNAS_CONTROL_PLANE_URL` is not set, the node defaults to +`https://api.betternas.com`. diff --git a/apps/node-agent/cmd/node-agent/control_plane.go b/apps/node-agent/cmd/node-agent/control_plane.go index 53b6531..570bbec 100644 --- a/apps/node-agent/cmd/node-agent/control_plane.go +++ b/apps/node-agent/cmd/node-agent/control_plane.go @@ -59,9 +59,9 @@ type nodeHeartbeatRequest struct { } func bootstrapNodeAgentFromEnv(exportPaths []string) (bootstrapResult, error) { - controlPlaneURL, err := requiredEnv("BETTERNAS_CONTROL_PLANE_URL") - if err != nil { - return bootstrapResult{}, err + controlPlaneURL := strings.TrimSpace(env("BETTERNAS_CONTROL_PLANE_URL", "https://api.betternas.com")) + if controlPlaneURL == "" { + return bootstrapResult{}, fmt.Errorf("BETTERNAS_CONTROL_PLANE_URL is required") } username, err := requiredEnv("BETTERNAS_USERNAME") diff --git a/apps/web/app/page.tsx b/apps/web/app/page.tsx index 867c3f7..abe1a38 100644 --- a/apps/web/app/page.tsx +++ b/apps/web/app/page.tsx @@ -147,11 +147,18 @@ export default function Home() { -
-                  
-                    {`BETTERNAS_USERNAME=${user.username} BETTERNAS_PASSWORD=... BETTERNAS_EXPORT_PATH=/path/to/export betternas-node`}
-                  
-                
+
+
+                    
+                      {`curl -fsSL https://raw.githubusercontent.com/harivansh-afk/betterNAS/main/scripts/install-betternas-node.sh | sh`}
+                    
+                  
+
+                    
+                      {`BETTERNAS_USERNAME=${user.username} BETTERNAS_PASSWORD=... BETTERNAS_EXPORT_PATH=/path/to/export BETTERNAS_NODE_DIRECT_ADDRESS=https://your-public-node-url betternas-node`}
+                    
+                  
+
)} diff --git a/scripts/install-betternas-node.sh b/scripts/install-betternas-node.sh new file mode 100755 index 0000000..280d536 --- /dev/null +++ b/scripts/install-betternas-node.sh @@ -0,0 +1,70 @@ +#!/usr/bin/env bash + +set -euo pipefail + +repo="${BETTERNAS_NODE_REPO:-harivansh-afk/betterNAS}" +binary_name="betternas-node" +install_dir="${BETTERNAS_INSTALL_DIR:-$HOME/.local/bin}" +version="${BETTERNAS_NODE_VERSION:-}" +release_api_url="${BETTERNAS_NODE_RELEASE_API_URL:-https://api.github.com/repos/${repo}/releases/latest}" +download_base_url="${BETTERNAS_NODE_DOWNLOAD_BASE_URL:-https://github.com/${repo}/releases/download}" + +if [[ -z "$version" ]]; then + version="$( + curl -fsSL "${release_api_url}" | \ + python3 -c 'import json,sys; print(json.load(sys.stdin)["tag_name"])' + )" +fi + +os_name="$(uname -s)" +arch_name="$(uname -m)" + +case "$os_name" in + Darwin) os="darwin" ;; + Linux) os="linux" ;; + *) + echo "Unsupported OS: $os_name" >&2 + exit 1 + ;; +esac + +case "$arch_name" in + x86_64|amd64) arch="amd64" ;; + arm64|aarch64) arch="arm64" ;; + *) + echo "Unsupported architecture: $arch_name" >&2 + exit 1 + ;; +esac + +archive_name="${binary_name}_${version}_${os}_${arch}.tar.gz" +download_url="${download_base_url}/${version}/${archive_name}" + +tmp_dir="$(mktemp -d)" +cleanup() { + rm -rf "$tmp_dir" +} +trap cleanup EXIT + +echo "Downloading ${download_url}" +curl -fsSL "$download_url" -o "${tmp_dir}/${archive_name}" + +mkdir -p "$install_dir" +tar -xzf "${tmp_dir}/${archive_name}" -C "$tmp_dir" +install -m 0755 "${tmp_dir}/${binary_name}" "${install_dir}/${binary_name}" + +cat <