installer
This commit is contained in:
@@ -19,6 +19,22 @@ Built from the [pear-app-template](https://github.com/snxraven) HyperDHT + proto
|
||||
|
||||
## Quick start
|
||||
|
||||
### One-line installer (recommended)
|
||||
|
||||
```bash
|
||||
curl -fsSL https://git.ssh.surf/snxraven/peardata/raw/branch/main/scripts/install.sh | bash
|
||||
```
|
||||
|
||||
```bash
|
||||
# Server only (Linux Bare + systemd → /opt/peardata)
|
||||
curl -fsSL https://git.ssh.surf/snxraven/peardata/raw/branch/main/scripts/install.sh | bash -s -- --server --yes
|
||||
|
||||
# Desktop client only
|
||||
curl -fsSL https://git.ssh.surf/snxraven/peardata/raw/branch/main/scripts/install.sh | bash -s -- --client --yes
|
||||
```
|
||||
|
||||
### From source
|
||||
|
||||
```bash
|
||||
cd peardata
|
||||
npm install
|
||||
@@ -33,9 +49,9 @@ curl -s 'http://127.0.0.1:19999/api/v3/data?chart=system.cpu&after=-30&points=30
|
||||
# Terminal C — mint an operator invite (optional)
|
||||
npm run mint-invite -- operator
|
||||
|
||||
# Terminal D — Pear desktop
|
||||
# Terminal D — Pear / Electron desktop
|
||||
npm start
|
||||
# or: pear run -d .
|
||||
# or: npm run start:client
|
||||
```
|
||||
|
||||
In the UI, paste the **agent public key** (viewer) or a **`pd1.` invite**.
|
||||
|
||||
+11
-8
@@ -1,22 +1,25 @@
|
||||
[Unit]
|
||||
Description=PearData PearMonitor agent (HyperDHT + REST metrics)
|
||||
Documentation=https://git.ssh.surf/snxraven/peardata
|
||||
After=network-online.target
|
||||
Wants=network-online.target
|
||||
|
||||
[Service]
|
||||
Type=simple
|
||||
WorkingDirectory=/opt/peardata
|
||||
EnvironmentFile=-/opt/peardata/.env
|
||||
ExecStart=/usr/bin/node /opt/peardata/bin/peardata-server.mjs
|
||||
ExecStartPost=/usr/bin/node /opt/peardata/scripts/healthcheck.js
|
||||
ExecStart=/opt/peardata/peardata-server
|
||||
Restart=on-failure
|
||||
RestartSec=3
|
||||
# Hardening
|
||||
RestartSec=5
|
||||
TimeoutStartSec=30
|
||||
TimeoutStopSec=30
|
||||
User=peardata
|
||||
Group=peardata
|
||||
NoNewPrivileges=true
|
||||
PrivateTmp=true
|
||||
ProtectSystem=strict
|
||||
ProtectHome=true
|
||||
ReadWritePaths=/opt/peardata/data /opt/peardata/.env
|
||||
Environment=NODE_ENV=production
|
||||
EnvironmentFile=-/opt/peardata/.env
|
||||
# Identity + HyperDB / metric buffers
|
||||
ReadWritePaths=/opt/peardata
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
|
||||
@@ -61,6 +61,7 @@ DRY_RUN=1 bash scripts/gitea-rolling-release.sh
|
||||
|
||||
| Script | Role |
|
||||
|--------|------|
|
||||
| `scripts/install.sh` | End-user installer (server systemd + desktop client) |
|
||||
| `scripts/hosts.cjs` | Host lists (`SERVER_LINUX`, `ALL_64`) |
|
||||
| `scripts/make.cjs` | Orchestrate server/client builds |
|
||||
| `scripts/bare-standalone.cjs` | Pack Bare `peardata-server` |
|
||||
|
||||
+44
-15
@@ -2,24 +2,51 @@
|
||||
|
||||
## Requirements
|
||||
|
||||
- Node.js **≥ 20**
|
||||
- Pear runtime (for desktop): install via Holepunch/Pear docs
|
||||
- Linux recommended for full net/disk `/proc` collectors (macOS gets CPU/RAM/load)
|
||||
- **Installer path:** Linux (server) and/or Linux/macOS (desktop client); `curl` + `tar`
|
||||
- **From source:** Node.js **≥ 20**; Pear runtime optional for `pear run`
|
||||
- Linux recommended for full net/disk `/proc` collectors
|
||||
|
||||
## Install
|
||||
## One-line installer (recommended)
|
||||
|
||||
Pulls **`rolling`** release binaries (same forge as PearDock-style installs):
|
||||
|
||||
```bash
|
||||
curl -fsSL https://git.ssh.surf/snxraven/peardata/raw/branch/main/scripts/install.sh | bash
|
||||
```
|
||||
|
||||
Non-interactive:
|
||||
|
||||
```bash
|
||||
# Linux agent → /opt/peardata + peardata.service
|
||||
curl -fsSL …/scripts/install.sh | bash -s -- --server --yes
|
||||
|
||||
# Desktop client
|
||||
curl -fsSL …/scripts/install.sh | bash -s -- --client --yes
|
||||
```
|
||||
|
||||
| Role | What you get |
|
||||
|------|----------------|
|
||||
| Server | Bare `peardata-server` in `/opt/peardata`, user `peardata`, systemd unit |
|
||||
| Client | Electron app (`~/Applications/peardata.app` on macOS, `~/.local/share/peardata` on Linux) |
|
||||
|
||||
Overrides: `PEARDATA_GITEA_URL`, `PEARDATA_OWNER`, `PEARDATA_REPO`, `PEARDATA_TAG`, `PEARDATA_VERSION`, `PEARDATA_SERVER_DIR`, `PEARDATA_YES=1`.
|
||||
|
||||
After the server starts once:
|
||||
|
||||
```bash
|
||||
sudo grep -E '^(SERVER_PUBLIC_KEY|SERVER_SEED)=' /opt/peardata/.env
|
||||
curl -s http://127.0.0.1:19999/api/v3/info
|
||||
```
|
||||
|
||||
## Install from source
|
||||
|
||||
```bash
|
||||
cd peardata
|
||||
npm install
|
||||
cp .env.example .env # optional
|
||||
```
|
||||
|
||||
Copy env template if needed:
|
||||
|
||||
```bash
|
||||
cp .env.example .env
|
||||
```
|
||||
|
||||
## Run the agent
|
||||
## Run the agent (source)
|
||||
|
||||
```bash
|
||||
npm run start:server
|
||||
@@ -83,17 +110,19 @@ npm start
|
||||
|
||||
## systemd (Linux)
|
||||
|
||||
Prefer the [one-line installer](#one-line-installer-recommended) (`--server`). It installs the Bare binary and writes `peardata.service`.
|
||||
|
||||
Manual (matches installer layout):
|
||||
|
||||
```bash
|
||||
sudo mkdir -p /opt/peardata
|
||||
sudo rsync -a ./ /opt/peardata/ --exclude node_modules
|
||||
cd /opt/peardata && sudo npm ci --omit=dev
|
||||
# After placing peardata-server + .env under /opt/peardata
|
||||
sudo cp deploy/peardata.service /etc/systemd/system/
|
||||
sudo systemctl daemon-reload
|
||||
sudo systemctl enable --now peardata
|
||||
journalctl -u peardata -f
|
||||
```
|
||||
|
||||
Ensure `ReadWritePaths` can write `/opt/peardata/data` and `.env`.
|
||||
From-source Node agents can still use `npm run start:server` under a custom unit; packaged releases use `/opt/peardata/peardata-server`.
|
||||
|
||||
## Roles cheat sheet
|
||||
|
||||
|
||||
@@ -9,6 +9,14 @@
|
||||
|
||||
Same layout as PearDock’s rolling pipeline, except PearData agents ship **Linux-only**.
|
||||
|
||||
## Installer
|
||||
|
||||
```bash
|
||||
curl -fsSL https://git.ssh.surf/snxraven/peardata/raw/branch/main/scripts/install.sh | bash
|
||||
```
|
||||
|
||||
Script: [`scripts/install.sh`](../scripts/install.sh) · unit: [`deploy/peardata.service`](../deploy/peardata.service)
|
||||
|
||||
## Local builds
|
||||
|
||||
```bash
|
||||
|
||||
+2
-1
@@ -64,7 +64,8 @@
|
||||
"make:client:win32-x64": "electron-forge package --platform win32 --arch x64",
|
||||
"make:client:win32-arm64": "electron-forge package --platform win32 --arch arm64",
|
||||
"sign:macos": "node scripts/sign-macos-app.cjs",
|
||||
"release:rolling": "bash scripts/gitea-rolling-release.sh"
|
||||
"release:rolling": "bash scripts/gitea-rolling-release.sh",
|
||||
"install:help": "bash scripts/install.sh --help"
|
||||
},
|
||||
"dependencies": {
|
||||
"b4a": "^1.8.1",
|
||||
|
||||
@@ -186,6 +186,14 @@ cat >"$DIST/RELEASE_NOTES.md" <<EOF
|
||||
| **Server** (Bare) | \`linux-x64\`, \`linux-arm64\` only |
|
||||
| **Client** (Electron) | \`linux-x64\`, \`linux-arm64\`, \`darwin-x64\`, \`darwin-arm64\`, \`win32-x64\`, \`win32-arm64\` |
|
||||
|
||||
## Installer
|
||||
|
||||
\`\`\`bash
|
||||
curl -fsSL ${GITEA_URL%/}/${GITEA_OWNER}/${GITEA_REPO}/raw/branch/main/scripts/install.sh | bash
|
||||
# or: … | bash -s -- --server --yes
|
||||
# or: … | bash -s -- --client --yes
|
||||
\`\`\`
|
||||
|
||||
## Server (Bare standalone)
|
||||
|
||||
\`\`\`bash
|
||||
|
||||
Executable
+638
@@ -0,0 +1,638 @@
|
||||
#!/usr/bin/env bash
|
||||
# peardata interactive installer
|
||||
#
|
||||
# One-liner (from this repo on Gitea):
|
||||
# curl -fsSL https://git.ssh.surf/snxraven/peardata/raw/branch/main/scripts/install.sh | bash
|
||||
#
|
||||
# Optional install host (if you mirror the script):
|
||||
# curl -fsSL https://install.peardata.boats | bash
|
||||
#
|
||||
# Non-interactive:
|
||||
# curl -fsSL …/install.sh | bash -s -- --server --yes
|
||||
# curl -fsSL …/install.sh | bash -s -- --client --yes
|
||||
# PEARDATA_ROLE=server bash scripts/install.sh --yes
|
||||
#
|
||||
# Env overrides:
|
||||
# PEARDATA_ROLE=server|client|both
|
||||
# PEARDATA_VERSION / PEARDATA_TAG=rolling
|
||||
# PEARDATA_GITEA_URL=https://git.ssh.surf
|
||||
# PEARDATA_OWNER=snxraven PEARDATA_REPO=peardata
|
||||
# PEARDATA_SERVER_DIR=/opt/peardata
|
||||
# PEARDATA_CLIENT_DIR=... (default: ~/.local/share/peardata or ~/Applications)
|
||||
# PEARDATA_YES=1 — assume defaults / non-interactive where possible
|
||||
#
|
||||
set -euo pipefail
|
||||
|
||||
# ─── defaults ───────────────────────────────────────────────────────────────
|
||||
GITEA_URL="${PEARDATA_GITEA_URL:-https://git.ssh.surf}"
|
||||
OWNER="${PEARDATA_OWNER:-snxraven}"
|
||||
REPO="${PEARDATA_REPO:-peardata}"
|
||||
TAG="${PEARDATA_TAG:-rolling}"
|
||||
VERSION="${PEARDATA_VERSION:-}" # empty → resolve from release assets
|
||||
ROLE="${PEARDATA_ROLE:-}"
|
||||
ASSUME_YES="${PEARDATA_YES:-0}"
|
||||
SERVER_DIR="${PEARDATA_SERVER_DIR:-/opt/peardata}"
|
||||
TMPDIR_ROOT="${TMPDIR:-/tmp}"
|
||||
INSTALL_TMP=""
|
||||
|
||||
# ─── colors / UI ────────────────────────────────────────────────────────────
|
||||
if [[ -t 1 ]] && command -v tput >/dev/null 2>&1 && [[ $(tput colors 2>/dev/null || echo 0) -ge 8 ]]; then
|
||||
C_TEAL="$(tput setaf 6)"
|
||||
C_BOLD="$(tput bold)"
|
||||
C_DIM="$(tput dim)"
|
||||
C_RED="$(tput setaf 1)"
|
||||
C_GREEN="$(tput setaf 2)"
|
||||
C_YELLOW="$(tput setaf 3)"
|
||||
C_RESET="$(tput sgr0)"
|
||||
else
|
||||
C_TEAL="" C_BOLD="" C_DIM="" C_RED="" C_GREEN="" C_YELLOW="" C_RESET=""
|
||||
fi
|
||||
|
||||
log() { printf '%s[peardata]%s %s\n' "$C_TEAL" "$C_RESET" "$*"; }
|
||||
ok() { printf '%s[peardata]%s %s✓%s %s\n' "$C_TEAL" "$C_RESET" "$C_GREEN" "$C_RESET" "$*"; }
|
||||
warn() { printf '%s[peardata]%s %s!%s %s\n' "$C_TEAL" "$C_RESET" "$C_YELLOW" "$C_RESET" "$*" >&2; }
|
||||
err() { printf '%s[peardata]%s %s✗%s %s\n' "$C_TEAL" "$C_RESET" "$C_RED" "$C_RESET" "$*" >&2; }
|
||||
die() { err "$*"; exit 1; }
|
||||
|
||||
banner() {
|
||||
cat <<EOF
|
||||
|
||||
${C_TEAL}${C_BOLD} ╔═══════════════════════════════════════╗
|
||||
║ p e a r d a t a ║
|
||||
║ P2P real-time host monitoring ║
|
||||
╚═══════════════════════════════════════╝${C_RESET}
|
||||
|
||||
EOF
|
||||
}
|
||||
|
||||
need_cmd() {
|
||||
command -v "$1" >/dev/null 2>&1 || die "Required command not found: $1"
|
||||
}
|
||||
|
||||
# ─── detect host ────────────────────────────────────────────────────────────
|
||||
detect_os() {
|
||||
local u
|
||||
u="$(uname -s 2>/dev/null || echo unknown)"
|
||||
case "$u" in
|
||||
Linux*) echo linux ;;
|
||||
Darwin*) echo darwin ;;
|
||||
MINGW*|MSYS*|CYGWIN*) echo win32 ;;
|
||||
*) echo "unknown" ;;
|
||||
esac
|
||||
}
|
||||
|
||||
detect_arch() {
|
||||
local m
|
||||
m="$(uname -m 2>/dev/null || echo unknown)"
|
||||
case "$m" in
|
||||
x86_64|amd64) echo x64 ;;
|
||||
aarch64|arm64) echo arm64 ;;
|
||||
armv7*|armhf) die "32-bit / armv7 is not supported (64-bit only)" ;;
|
||||
i386|i686) die "32-bit x86 is not supported (64-bit only)" ;;
|
||||
*) die "Unsupported architecture: $m" ;;
|
||||
esac
|
||||
}
|
||||
|
||||
host_triple() {
|
||||
local os arch
|
||||
os="$(detect_os)"
|
||||
arch="$(detect_arch)"
|
||||
[[ "$os" == "unknown" ]] && die "Unsupported OS: $(uname -s)"
|
||||
[[ "$os" == "win32" ]] && die "Windows install via bash is limited — download a .tar.gz from the rolling release instead."
|
||||
echo "${os}-${arch}"
|
||||
}
|
||||
|
||||
# ─── args ───────────────────────────────────────────────────────────────────
|
||||
parse_args() {
|
||||
while [[ $# -gt 0 ]]; do
|
||||
case "$1" in
|
||||
--server|-s) ROLE=server; shift ;;
|
||||
--client|-c) ROLE=client; shift ;;
|
||||
--both|-b) ROLE=both; shift ;;
|
||||
--yes|-y) ASSUME_YES=1; shift ;;
|
||||
--tag) TAG="${2:-}"; shift 2 ;;
|
||||
--version) VERSION="${2:-}"; shift 2 ;;
|
||||
--server-dir) SERVER_DIR="${2:-}"; shift 2 ;;
|
||||
--help|-h)
|
||||
cat <<EOF
|
||||
Usage: install.sh [options]
|
||||
|
||||
--server, -s Install peardata-server (Linux Bare binary + systemd)
|
||||
--client, -c Install peardata desktop client
|
||||
--both, -b Install both
|
||||
--yes, -y Non-interactive defaults
|
||||
--tag TAG Release tag (default: rolling)
|
||||
--version VER Pin asset version (default: auto from release)
|
||||
--server-dir DIR Server install path (default: /opt/peardata)
|
||||
--help This help
|
||||
|
||||
One-liner:
|
||||
curl -fsSL ${GITEA_URL}/${OWNER}/${REPO}/raw/branch/main/scripts/install.sh | bash
|
||||
|
||||
Non-interactive:
|
||||
curl -fsSL …/install.sh | bash -s -- --server --yes
|
||||
curl -fsSL …/install.sh | bash -s -- --client --yes
|
||||
EOF
|
||||
exit 0
|
||||
;;
|
||||
*) die "Unknown option: $1 (try --help)" ;;
|
||||
esac
|
||||
done
|
||||
}
|
||||
|
||||
# ─── prompts ────────────────────────────────────────────────────────────────
|
||||
ask() {
|
||||
local prompt="$1" default="${2:-}" reply
|
||||
if [[ "$ASSUME_YES" == "1" ]]; then
|
||||
echo "${default}"
|
||||
return
|
||||
fi
|
||||
if [[ ! -t 0 ]]; then
|
||||
if [[ -r /dev/tty ]]; then
|
||||
if [[ -n "$default" ]]; then
|
||||
printf '%s [%s]: ' "$prompt" "$default" >/dev/tty
|
||||
else
|
||||
printf '%s: ' "$prompt" >/dev/tty
|
||||
fi
|
||||
IFS= read -r reply </dev/tty || true
|
||||
else
|
||||
echo "${default}"
|
||||
return
|
||||
fi
|
||||
else
|
||||
if [[ -n "$default" ]]; then
|
||||
read -r -p "${prompt} [${default}]: " reply || true
|
||||
else
|
||||
read -r -p "${prompt}: " reply || true
|
||||
fi
|
||||
fi
|
||||
if [[ -z "${reply// }" ]]; then
|
||||
echo "$default"
|
||||
else
|
||||
echo "$reply"
|
||||
fi
|
||||
}
|
||||
|
||||
ask_choice() {
|
||||
local prompt="$1" default="$2"
|
||||
local ans
|
||||
ans="$(ask "$prompt" "$default")"
|
||||
echo "$ans" | tr '[:upper:]' '[:lower:]' | tr -d '[:space:]'
|
||||
}
|
||||
|
||||
confirm() {
|
||||
local prompt="$1" default="${2:-y}"
|
||||
local ans
|
||||
ans="$(ask "$prompt (y/n)" "$default")"
|
||||
case "$(echo "$ans" | tr '[:upper:]' '[:lower:]')" in
|
||||
y|yes) return 0 ;;
|
||||
*) return 1 ;;
|
||||
esac
|
||||
}
|
||||
|
||||
# ─── privileges / download ──────────────────────────────────────────────────
|
||||
have_sudo() {
|
||||
[[ "$(id -u)" -eq 0 ]] && return 0
|
||||
command -v sudo >/dev/null 2>&1
|
||||
}
|
||||
|
||||
run_root() {
|
||||
if [[ "$(id -u)" -eq 0 ]]; then
|
||||
"$@"
|
||||
elif command -v sudo >/dev/null 2>&1; then
|
||||
sudo "$@"
|
||||
else
|
||||
die "Need root privileges for: $*"
|
||||
fi
|
||||
}
|
||||
|
||||
download() {
|
||||
local url="$1" out="$2"
|
||||
log "Downloading $(basename "$url")…"
|
||||
if command -v curl >/dev/null 2>&1; then
|
||||
curl -fsSL --retry 3 --retry-delay 2 --connect-timeout 20 -o "$out" "$url" \
|
||||
|| die "Download failed: $url"
|
||||
elif command -v wget >/dev/null 2>&1; then
|
||||
wget -q -O "$out" "$url" || die "Download failed: $url"
|
||||
else
|
||||
die "Need curl or wget"
|
||||
fi
|
||||
}
|
||||
|
||||
json_get_assets() {
|
||||
local api="${GITEA_URL}/api/v1/repos/${OWNER}/${REPO}/releases/tags/${TAG}"
|
||||
local json
|
||||
json="$(curl -fsSL --connect-timeout 15 "$api" 2>/dev/null || true)"
|
||||
if [[ -z "$json" ]]; then
|
||||
return 1
|
||||
fi
|
||||
printf '%s' "$json" | tr ',' '\n' | sed -n 's/.*"name"[[:space:]]*:[[:space:]]*"\([^"]*\)".*/\1/p' | grep -E '^peardata-(server|client)-' || true
|
||||
}
|
||||
|
||||
resolve_version() {
|
||||
if [[ -n "$VERSION" ]]; then
|
||||
echo "$VERSION"
|
||||
return
|
||||
fi
|
||||
local assets name
|
||||
assets="$(json_get_assets || true)"
|
||||
if [[ -n "$assets" ]]; then
|
||||
name="$(printf '%s\n' "$assets" | grep -E "^peardata-server-[0-9]" | head -1 || true)"
|
||||
if [[ -z "$name" ]]; then
|
||||
name="$(printf '%s\n' "$assets" | grep -E "^peardata-client-[0-9]" | head -1 || true)"
|
||||
fi
|
||||
if [[ -n "$name" ]]; then
|
||||
# peardata-server-0.1.0-linux-x64.tar.gz
|
||||
echo "$name" | sed -E 's/^peardata-(server|client)-([0-9][^/]*)-(linux|darwin|win32)-.*/\2/'
|
||||
return
|
||||
fi
|
||||
fi
|
||||
local ver
|
||||
ver="$(curl -fsSL "${GITEA_URL}/${OWNER}/${REPO}/raw/branch/main/package.json" 2>/dev/null \
|
||||
| sed -n 's/.*"version"[[:space:]]*:[[:space:]]*"\([^"]*\)".*/\1/p' | head -1 || true)"
|
||||
if [[ -n "$ver" ]]; then
|
||||
echo "$ver"
|
||||
return
|
||||
fi
|
||||
die "Could not resolve release version — set PEARDATA_VERSION=x.y.z"
|
||||
}
|
||||
|
||||
asset_url() {
|
||||
local name="$1"
|
||||
echo "${GITEA_URL}/${OWNER}/${REPO}/releases/download/${TAG}/${name}"
|
||||
}
|
||||
|
||||
# ─── server install (Linux Bare binary + systemd) ───────────────────────────
|
||||
install_server() {
|
||||
local host="$1" version="$2"
|
||||
local os arch archive url tarball work bin_src unit_path
|
||||
|
||||
os="${host%%-*}"
|
||||
arch="${host#*-}"
|
||||
|
||||
[[ "$os" == "linux" ]] || die "Server install is supported on Linux only (detected: $os). Download a client for $host, or run the agent on a Linux host."
|
||||
|
||||
need_cmd tar
|
||||
if ! command -v curl >/dev/null 2>&1 && ! command -v wget >/dev/null 2>&1; then
|
||||
die "Need curl or wget"
|
||||
fi
|
||||
|
||||
archive="peardata-server-${version}-${host}.tar.gz"
|
||||
url="$(asset_url "$archive")"
|
||||
work="${INSTALL_TMP}/server"
|
||||
mkdir -p "$work"
|
||||
tarball="${work}/${archive}"
|
||||
download "$url" "$tarball"
|
||||
|
||||
if curl -fsSL --connect-timeout 10 -o "${tarball}.sha256" "${url}.sha256" 2>/dev/null; then
|
||||
if command -v sha256sum >/dev/null 2>&1; then
|
||||
(cd "$work" && sha256sum -c "$(basename "$tarball").sha256") && ok "Checksum OK" || warn "Checksum verify failed (continuing)"
|
||||
elif command -v shasum >/dev/null 2>&1; then
|
||||
(cd "$work" && shasum -a 256 -c "$(basename "$tarball").sha256") && ok "Checksum OK" || warn "Checksum verify failed (continuing)"
|
||||
fi
|
||||
fi
|
||||
|
||||
mkdir -p "${work}/extract"
|
||||
tar -xzf "$tarball" -C "${work}/extract"
|
||||
bin_src="$(find "${work}/extract" -type f -name 'peardata-server' | head -1 || true)"
|
||||
[[ -n "$bin_src" ]] || die "peardata-server binary not found in archive"
|
||||
chmod +x "$bin_src"
|
||||
|
||||
log "Installing server to ${SERVER_DIR} (requires root)…"
|
||||
have_sudo || die "Server install needs root/sudo"
|
||||
run_root mkdir -p "${SERVER_DIR}/data"
|
||||
run_root install -m 0755 "$bin_src" "${SERVER_DIR}/peardata-server"
|
||||
|
||||
if [[ ! -f "${SERVER_DIR}/.env" ]]; then
|
||||
run_root touch "${SERVER_DIR}/.env"
|
||||
run_root chmod 600 "${SERVER_DIR}/.env"
|
||||
fi
|
||||
|
||||
if ! id peardata >/dev/null 2>&1; then
|
||||
log "Creating system user peardata…"
|
||||
if command -v useradd >/dev/null 2>&1; then
|
||||
run_root useradd --system --home "$SERVER_DIR" --shell /usr/sbin/nologin peardata 2>/dev/null \
|
||||
|| run_root useradd --system --home "$SERVER_DIR" --shell /bin/false peardata
|
||||
elif command -v adduser >/dev/null 2>&1; then
|
||||
run_root adduser --system --home "$SERVER_DIR" --shell /usr/sbin/nologin --no-create-home peardata || true
|
||||
else
|
||||
die "Cannot create peardata user (need useradd or adduser)"
|
||||
fi
|
||||
fi
|
||||
id peardata >/dev/null 2>&1 || die "peardata user was not created"
|
||||
|
||||
run_root chown -R peardata:peardata "$SERVER_DIR" 2>/dev/null || run_root chown -R peardata "$SERVER_DIR"
|
||||
|
||||
unit_path="/etc/systemd/system/peardata.service"
|
||||
log "Writing ${unit_path}…"
|
||||
run_root tee "$unit_path" >/dev/null <<EOF
|
||||
[Unit]
|
||||
Description=PearData PearMonitor agent (HyperDHT + REST metrics)
|
||||
Documentation=${GITEA_URL}/${OWNER}/${REPO}
|
||||
After=network-online.target
|
||||
Wants=network-online.target
|
||||
|
||||
[Service]
|
||||
Type=simple
|
||||
WorkingDirectory=${SERVER_DIR}
|
||||
ExecStart=${SERVER_DIR}/peardata-server
|
||||
Restart=on-failure
|
||||
RestartSec=5
|
||||
TimeoutStartSec=30
|
||||
TimeoutStopSec=30
|
||||
User=peardata
|
||||
Group=peardata
|
||||
NoNewPrivileges=true
|
||||
PrivateTmp=true
|
||||
Environment=NODE_ENV=production
|
||||
EnvironmentFile=-${SERVER_DIR}/.env
|
||||
# Identity + HyperDB / ring buffers
|
||||
ReadWritePaths=${SERVER_DIR}
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
EOF
|
||||
|
||||
if command -v systemctl >/dev/null 2>&1; then
|
||||
run_root systemctl daemon-reload
|
||||
if confirm "Enable and start peardata.service now?" "y"; then
|
||||
run_root systemctl enable --now peardata.service
|
||||
ok "Service peardata.service is enabled and started"
|
||||
sleep 2
|
||||
run_root systemctl --no-pager --full status peardata.service || true
|
||||
log "Logs: journalctl -u peardata -f"
|
||||
else
|
||||
ok "Unit installed. Start later with: sudo systemctl enable --now peardata"
|
||||
fi
|
||||
else
|
||||
warn "systemctl not found — binary installed at ${SERVER_DIR}/peardata-server (start manually)"
|
||||
fi
|
||||
|
||||
local env_pk=""
|
||||
if [[ -f "${SERVER_DIR}/.env" ]]; then
|
||||
env_pk="$(run_root grep -E '^SERVER_PUBLIC_KEY=' "${SERVER_DIR}/.env" 2>/dev/null | head -1 | cut -d= -f2- | tr -d '[:space:]' || true)"
|
||||
fi
|
||||
|
||||
cat <<EOF
|
||||
|
||||
${C_BOLD}Server installed${C_RESET}
|
||||
Binary: ${SERVER_DIR}/peardata-server
|
||||
Config: ${SERVER_DIR}/.env ${C_BOLD}← check this file for your keys${C_RESET}
|
||||
Data: ${SERVER_DIR}/data
|
||||
Service: peardata.service
|
||||
REST: http://127.0.0.1:19999/api/v3/info (localhost by default)
|
||||
|
||||
${C_BOLD}Keys (written on first successful start)${C_RESET}
|
||||
After peardata has started once:
|
||||
|
||||
sudo grep -E '^(SERVER_PUBLIC_KEY|SERVER_SEED)=' ${SERVER_DIR}/.env
|
||||
|
||||
${C_BOLD}SERVER_PUBLIC_KEY${C_RESET} 64 hex — share with clients (viewer if used alone)
|
||||
${C_BOLD}SERVER_SEED${C_RESET} 64 hex — ${C_BOLD}admin only${C_RESET}; never share with operators
|
||||
|
||||
EOF
|
||||
|
||||
if [[ -n "$env_pk" && ${#env_pk} -eq 64 ]]; then
|
||||
cat <<EOF
|
||||
${C_GREEN}SERVER_PUBLIC_KEY (from .env):${C_RESET}
|
||||
${env_pk}
|
||||
|
||||
EOF
|
||||
else
|
||||
cat <<EOF
|
||||
If keys are missing, start/restart once then re-check .env:
|
||||
sudo systemctl enable --now peardata
|
||||
sudo systemctl restart peardata
|
||||
sudo grep -E '^(SERVER_PUBLIC_KEY|SERVER_SEED)=' ${SERVER_DIR}/.env
|
||||
|
||||
Journal (also prints the public key on boot):
|
||||
sudo journalctl -u peardata -n 80 --no-pager | grep -i publicKey
|
||||
|
||||
EOF
|
||||
fi
|
||||
|
||||
cat <<EOF
|
||||
${C_BOLD}Connect from the desktop client${C_RESET}
|
||||
· Public key only → viewer (read + subscribe)
|
||||
· Public key + SERVER_SEED → admin
|
||||
· Full pd1.… invite → role from invite (operators; no seed)
|
||||
|
||||
Docs: ${GITEA_URL}/${OWNER}/${REPO}
|
||||
|
||||
EOF
|
||||
}
|
||||
|
||||
# ─── client install ─────────────────────────────────────────────────────────
|
||||
default_client_dir() {
|
||||
local os="$1"
|
||||
case "$os" in
|
||||
darwin)
|
||||
echo "${PEARDATA_CLIENT_DIR:-$HOME/Applications}"
|
||||
;;
|
||||
*)
|
||||
echo "${PEARDATA_CLIENT_DIR:-$HOME/.local/share/peardata}"
|
||||
;;
|
||||
esac
|
||||
}
|
||||
|
||||
install_desktop_entry_linux() {
|
||||
local bin="$1" icon_src="${2:-}"
|
||||
local apps="$HOME/.local/share/applications"
|
||||
local icons="$HOME/.local/share/icons/hicolor/256x256/apps"
|
||||
mkdir -p "$apps"
|
||||
if [[ -n "$icon_src" && -f "$icon_src" ]]; then
|
||||
mkdir -p "$icons"
|
||||
cp -f "$icon_src" "$icons/peardata.png" 2>/dev/null || true
|
||||
fi
|
||||
cat >"${apps}/peardata.desktop" <<EOF
|
||||
[Desktop Entry]
|
||||
Type=Application
|
||||
Name=PearData
|
||||
Comment=P2P real-time host monitoring
|
||||
Exec=${bin}
|
||||
Icon=peardata
|
||||
Terminal=false
|
||||
Categories=System;Monitor;Network;
|
||||
StartupWMClass=peardata
|
||||
EOF
|
||||
if command -v update-desktop-database >/dev/null 2>&1; then
|
||||
update-desktop-database "$apps" 2>/dev/null || true
|
||||
fi
|
||||
ok "Desktop entry: ${apps}/peardata.desktop"
|
||||
}
|
||||
|
||||
install_client() {
|
||||
local host="$1" version="$2"
|
||||
local os arch archive url tarball work extract client_dir bin app
|
||||
|
||||
os="${host%%-*}"
|
||||
arch="${host#*-}"
|
||||
client_dir="$(default_client_dir "$os")"
|
||||
|
||||
need_cmd tar
|
||||
archive="peardata-client-${version}-${host}.tar.gz"
|
||||
url="$(asset_url "$archive")"
|
||||
work="${INSTALL_TMP}/client"
|
||||
mkdir -p "$work"
|
||||
tarball="${work}/${archive}"
|
||||
download "$url" "$tarball"
|
||||
|
||||
if curl -fsSL --connect-timeout 10 -o "${tarball}.sha256" "${url}.sha256" 2>/dev/null; then
|
||||
if command -v sha256sum >/dev/null 2>&1; then
|
||||
(cd "$work" && sha256sum -c "$(basename "$tarball").sha256") && ok "Checksum OK" || warn "Checksum verify failed (continuing)"
|
||||
elif command -v shasum >/dev/null 2>&1; then
|
||||
(cd "$work" && shasum -a 256 -c "$(basename "$tarball").sha256") && ok "Checksum OK" || warn "Checksum verify failed (continuing)"
|
||||
fi
|
||||
fi
|
||||
|
||||
extract="${work}/extract"
|
||||
rm -rf "$extract"
|
||||
mkdir -p "$extract"
|
||||
tar -xzf "$tarball" -C "$extract"
|
||||
|
||||
# Archive contains peardata-<host>/... (or legacy PearData-<host>)
|
||||
local payload
|
||||
payload="$(find "$extract" -maxdepth 1 -type d \( -name 'peardata-*' -o -name 'PearData-*' \) | head -1 || true)"
|
||||
[[ -n "$payload" ]] || payload="$extract"
|
||||
[[ -d "$payload" ]] || die "Unexpected client archive layout"
|
||||
|
||||
case "$os" in
|
||||
darwin)
|
||||
app="$(find "$payload" -maxdepth 3 \( -name 'peardata.app' -o -name 'PearData.app' \) -type d | head -1 || true)"
|
||||
[[ -n "$app" ]] || die "peardata.app not found in client archive"
|
||||
mkdir -p "$client_dir"
|
||||
local dest_app="${client_dir}/peardata.app"
|
||||
rm -rf "$dest_app"
|
||||
cp -a "$app" "$dest_app"
|
||||
app="$dest_app"
|
||||
if [[ -d "${client_dir}/peardata" && ! -d "${client_dir}/peardata/Contents" ]]; then
|
||||
rm -rf "${client_dir}/peardata"
|
||||
fi
|
||||
xattr -cr "$app" 2>/dev/null || true
|
||||
ok "Client app: $app"
|
||||
if confirm "Open PearData now?" "y"; then
|
||||
open "$app" || true
|
||||
fi
|
||||
cat <<EOF
|
||||
|
||||
${C_BOLD}Client installed (macOS)${C_RESET}
|
||||
App: $app
|
||||
|
||||
If Gatekeeper blocks it: right-click → Open, or:
|
||||
xattr -cr "$app"
|
||||
|
||||
Connect:
|
||||
· Agent public key (SERVER_PUBLIC_KEY) → viewer
|
||||
· Public key + SERVER_SEED → admin (never share the seed)
|
||||
· Full pd1. invite from an admin → operator without the seed
|
||||
|
||||
On the agent host:
|
||||
sudo grep -E '^(SERVER_PUBLIC_KEY|SERVER_SEED)=' /opt/peardata/.env
|
||||
|
||||
EOF
|
||||
;;
|
||||
linux)
|
||||
mkdir -p "$client_dir"
|
||||
rm -rf "${client_dir:?}/"* 2>/dev/null || true
|
||||
cp -a "$payload"/. "$client_dir"/
|
||||
bin="$(find "$client_dir" -type f -name 'peardata-client' | head -1 || true)"
|
||||
[[ -n "$bin" ]] || die "peardata-client binary not found in archive"
|
||||
chmod +x "$bin"
|
||||
local bindir="$HOME/.local/bin"
|
||||
mkdir -p "$bindir"
|
||||
ln -sfn "$bin" "${bindir}/peardata-client"
|
||||
ln -sfn "$bin" "${bindir}/peardata"
|
||||
local icon
|
||||
icon="$(find "$client_dir" -type f \( -name 'icon.png' -o -name '*256*.png' \) 2>/dev/null | head -1 || true)"
|
||||
install_desktop_entry_linux "$bin" "$icon"
|
||||
ok "Client binary: $bin"
|
||||
ok "Launcher: ${bindir}/peardata-client (ensure ~/.local/bin is on PATH)"
|
||||
cat <<EOF
|
||||
|
||||
${C_BOLD}Client installed (Linux)${C_RESET}
|
||||
Run: peardata-client
|
||||
or: ${bin}
|
||||
|
||||
Connect:
|
||||
· Agent public key (from /opt/peardata/.env → SERVER_PUBLIC_KEY) → viewer
|
||||
· Public key + SERVER_SEED → admin
|
||||
· Full pd1. invite → operator/admin without the seed
|
||||
|
||||
On the agent host: sudo grep -E '^(SERVER_PUBLIC_KEY|SERVER_SEED)=' /opt/peardata/.env
|
||||
|
||||
EOF
|
||||
if confirm "Launch peardata-client now?" "n"; then
|
||||
nohup "$bin" >/dev/null 2>&1 &
|
||||
fi
|
||||
;;
|
||||
*)
|
||||
die "Client install not automated for OS=$os"
|
||||
;;
|
||||
esac
|
||||
}
|
||||
|
||||
# ─── main ───────────────────────────────────────────────────────────────────
|
||||
cleanup() {
|
||||
if [[ -n "${INSTALL_TMP}" && -d "${INSTALL_TMP}" ]]; then
|
||||
rm -rf "${INSTALL_TMP}"
|
||||
fi
|
||||
}
|
||||
trap cleanup EXIT
|
||||
|
||||
main() {
|
||||
parse_args "$@"
|
||||
banner
|
||||
|
||||
need_cmd uname
|
||||
local host os arch version
|
||||
host="$(host_triple)"
|
||||
os="${host%%-*}"
|
||||
arch="${host#*-}"
|
||||
|
||||
log "Detected host: ${C_BOLD}${host}${C_RESET} ($(uname -s) $(uname -m))"
|
||||
log "Release: ${GITEA_URL}/${OWNER}/${REPO} @ ${TAG}"
|
||||
|
||||
version="$(resolve_version)"
|
||||
log "Version: ${version}"
|
||||
|
||||
if [[ -z "$ROLE" ]]; then
|
||||
echo "What do you want to install?"
|
||||
echo " ${C_BOLD}1${C_RESET}) Server — PearMonitor agent (Linux Bare binary + systemd)"
|
||||
echo " ${C_BOLD}2${C_RESET}) Client — Desktop GUI (connect with a public key)"
|
||||
echo " ${C_BOLD}3${C_RESET}) Both"
|
||||
echo
|
||||
local choice
|
||||
# Default client on macOS (no Linux server binary); server on Linux
|
||||
local def="2"
|
||||
[[ "$os" == "linux" ]] && def="1"
|
||||
choice="$(ask_choice "Choose 1/2/3" "$def")"
|
||||
case "$choice" in
|
||||
1|s|server) ROLE=server ;;
|
||||
2|c|client) ROLE=client ;;
|
||||
3|b|both) ROLE=both ;;
|
||||
*) die "Invalid choice: $choice" ;;
|
||||
esac
|
||||
fi
|
||||
|
||||
INSTALL_TMP="$(mktemp -d "${TMPDIR_ROOT}/peardata-install.XXXXXX")"
|
||||
|
||||
case "$ROLE" in
|
||||
server)
|
||||
install_server "$host" "$version"
|
||||
;;
|
||||
client)
|
||||
install_client "$host" "$version"
|
||||
;;
|
||||
both)
|
||||
install_server "$host" "$version"
|
||||
install_client "$host" "$version"
|
||||
;;
|
||||
*)
|
||||
die "Unknown role: $ROLE (use server|client|both)"
|
||||
;;
|
||||
esac
|
||||
|
||||
ok "All done. Docs: ${GITEA_URL}/${OWNER}/${REPO}"
|
||||
}
|
||||
|
||||
main "$@"
|
||||
Reference in New Issue
Block a user