750 lines
25 KiB
Bash
Executable File
750 lines
25 KiB
Bash
Executable File
#!/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}"
|
|
}
|
|
|
|
# First existing Docker Engine unix socket (empty if none).
|
|
detect_docker_socket() {
|
|
local p
|
|
for p in \
|
|
"${PEARDATA_DOCKER_SOCKET:-}" \
|
|
/var/run/docker.sock \
|
|
/run/docker.sock \
|
|
/var/run/podman/podman.sock \
|
|
/run/podman/podman.sock
|
|
do
|
|
[[ -n "$p" && -S "$p" ]] || continue
|
|
printf '%s\n' "$p"
|
|
return 0
|
|
done
|
|
return 1
|
|
}
|
|
|
|
# ─── 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"
|
|
|
|
# Host journal access for Logs tab (journalctl as peardata)
|
|
if getent group systemd-journal >/dev/null 2>&1; then
|
|
log "Adding peardata to systemd-journal for host log access…"
|
|
if command -v usermod >/dev/null 2>&1; then
|
|
run_root usermod -aG systemd-journal peardata || warn "usermod -aG systemd-journal peardata failed"
|
|
elif command -v gpasswd >/dev/null 2>&1; then
|
|
run_root gpasswd -a peardata systemd-journal || warn "gpasswd -a peardata systemd-journal failed"
|
|
else
|
|
warn "Could not add peardata to systemd-journal (no usermod/gpasswd)"
|
|
fi
|
|
else
|
|
warn "Group systemd-journal not found — host Journal in Logs may be unavailable"
|
|
fi
|
|
|
|
# Ensure journal is enabled in .env (default on; operators may set PEARDATA_JOURNAL=0)
|
|
if ! run_root grep -qE '^PEARDATA_JOURNAL=' "${SERVER_DIR}/.env" 2>/dev/null; then
|
|
log "Enabling PEARDATA_JOURNAL=1 in ${SERVER_DIR}/.env"
|
|
run_root sh -c "printf '\\n# Host journal for Logs tab (set to 0 to disable)\\nPEARDATA_JOURNAL=1\\n' >> '${SERVER_DIR}/.env'"
|
|
fi
|
|
|
|
# Docker socket access — container names (e.g. dozzle) + per-container metrics
|
|
local docker_detected=0
|
|
local docker_sock=""
|
|
local docker_group_name=""
|
|
docker_sock="$(detect_docker_socket || true)"
|
|
if [[ -n "$docker_sock" ]]; then
|
|
docker_detected=1
|
|
elif getent group docker >/dev/null 2>&1; then
|
|
docker_detected=1
|
|
docker_sock="/var/run/docker.sock"
|
|
elif command -v docker >/dev/null 2>&1; then
|
|
docker_detected=1
|
|
docker_sock="/var/run/docker.sock"
|
|
fi
|
|
if getent group docker >/dev/null 2>&1; then
|
|
docker_group_name="docker"
|
|
fi
|
|
|
|
if [[ "$docker_detected" -eq 1 ]]; then
|
|
ok "Docker detected${docker_sock:+ (socket ${docker_sock})}"
|
|
if [[ -n "$docker_group_name" ]]; then
|
|
log "Adding peardata to ${docker_group_name} for Docker socket / container names…"
|
|
if command -v usermod >/dev/null 2>&1; then
|
|
run_root usermod -aG "$docker_group_name" peardata || warn "usermod -aG ${docker_group_name} peardata failed"
|
|
elif command -v gpasswd >/dev/null 2>&1; then
|
|
run_root gpasswd -a peardata "$docker_group_name" || warn "gpasswd -a peardata ${docker_group_name} failed"
|
|
else
|
|
warn "Could not add peardata to ${docker_group_name} (no usermod/gpasswd)"
|
|
fi
|
|
else
|
|
warn "Docker socket present but no 'docker' group — grant peardata read access to ${docker_sock} manually"
|
|
fi
|
|
if ! run_root grep -qE '^PEARDATA_DOCKER=' "${SERVER_DIR}/.env" 2>/dev/null; then
|
|
log "Enabling PEARDATA_DOCKER=1 in ${SERVER_DIR}/.env (container metrics + names)"
|
|
run_root sh -c "printf '\\n# Docker collector (names via socket; set to 0 to disable)\\nPEARDATA_DOCKER=1\\nPEARDATA_DOCKER_SOCKET=%s\\n' '${docker_sock}' >> '${SERVER_DIR}/.env'"
|
|
else
|
|
# Keep collector flag; refresh socket path if unset
|
|
if ! run_root grep -qE '^PEARDATA_DOCKER_SOCKET=' "${SERVER_DIR}/.env" 2>/dev/null; then
|
|
run_root sh -c "printf 'PEARDATA_DOCKER_SOCKET=%s\\n' '${docker_sock}' >> '${SERVER_DIR}/.env'"
|
|
fi
|
|
fi
|
|
else
|
|
log "Docker not detected — skipping socket access (install Docker later, then re-run installer or: usermod -aG docker peardata && PEARDATA_DOCKER=1)"
|
|
fi
|
|
|
|
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"
|
|
# systemd SupplementaryGroups replaces the user's supplementary set — list every group we need
|
|
local supp_groups=()
|
|
if getent group systemd-journal >/dev/null 2>&1; then
|
|
supp_groups+=("systemd-journal")
|
|
fi
|
|
if [[ "$docker_detected" -eq 1 && -n "$docker_group_name" ]]; then
|
|
supp_groups+=("$docker_group_name")
|
|
fi
|
|
local supp_groups_line=""
|
|
if [[ ${#supp_groups[@]} -gt 0 ]]; then
|
|
supp_groups_line="SupplementaryGroups=${supp_groups[*]}"
|
|
fi
|
|
local docker_after_line=""
|
|
local docker_wants_line=""
|
|
if [[ "$docker_detected" -eq 1 ]]; then
|
|
docker_after_line="After=docker.service"
|
|
docker_wants_line="Wants=docker.service"
|
|
fi
|
|
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
|
|
${docker_after_line}
|
|
${docker_wants_line}
|
|
|
|
[Service]
|
|
Type=simple
|
|
WorkingDirectory=${SERVER_DIR}
|
|
ExecStart=${SERVER_DIR}/peardata-server
|
|
Restart=on-failure
|
|
RestartSec=5
|
|
TimeoutStartSec=30
|
|
TimeoutStopSec=30
|
|
User=peardata
|
|
Group=peardata
|
|
${supp_groups_line}
|
|
NoNewPrivileges=true
|
|
PrivateTmp=true
|
|
Environment=NODE_ENV=production
|
|
Environment=PEARDATA_JOURNAL=1
|
|
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
|
|
|
|
local docker_summary="not detected (optional)"
|
|
if [[ "$docker_detected" -eq 1 ]]; then
|
|
docker_summary="enabled · peardata ∈ ${docker_group_name:-docker} · ${docker_sock} (human container names)"
|
|
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
|
|
Journal: peardata ∈ systemd-journal · PEARDATA_JOURNAL=1 (Logs tab)
|
|
Docker: ${docker_summary}
|
|
REST: http://127.0.0.1:18888/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 "$@"
|