mirror of
https://github.com/Quad4-Software/Reticulum-Go
synced 2026-08-29 23:48:44 -04:00
1066 lines
23 KiB
Bash
Executable file
1066 lines
23 KiB
Bash
Executable file
#!/bin/sh
|
|
# SPDX-License-Identifier: Apache-2.0
|
|
# Copyright (c) 2024-2026 Quad4.io
|
|
#
|
|
# Install reticulum-go from a GitHub release binary or by building source.
|
|
#
|
|
# Curl:
|
|
# curl -fsSL https://raw.githubusercontent.com/Quad4-Software/Reticulum-Go/master/install.sh | sh
|
|
# curl -fsSL https://raw.githubusercontent.com/Quad4-Software/Reticulum-Go/master/install.sh | sh -s -- --dry-run
|
|
# curl -fsSL https://raw.githubusercontent.com/Quad4-Software/Reticulum-Go/master/install.sh | sh -s -- --source
|
|
#
|
|
# Checkout:
|
|
# ./install.sh
|
|
# ./install.sh --dry-run
|
|
# ./install.sh --source --init systemd
|
|
#
|
|
# shellcheck shell=sh
|
|
|
|
set -eu
|
|
|
|
REPO="${RGO_REPO:-Quad4-Software/Reticulum-Go}"
|
|
GITHUB="${RGO_GITHUB:-https://github.com}"
|
|
GITHUB_API="${RGO_GITHUB_API:-https://api.github.com}"
|
|
GITHUB_RAW="${RGO_GITHUB_RAW:-https://raw.githubusercontent.com}"
|
|
MIN_GO="${RGO_MIN_GO:-1.26.5}"
|
|
BINARY_NAME="reticulum-go"
|
|
STATE_DIR="/var/lib/reticulum-go"
|
|
TOOL_LINKS="rgostatus rgoid rgoprobe rgopath rgocp rgox rnx rgosh rgopageserver rgoslow rgoselfcheck rgospeed rgodump rgosnap rgozen"
|
|
|
|
PREFIX="${PREFIX:-/usr/local}"
|
|
DESTDIR="${DESTDIR:-}"
|
|
BINDIR="${BINDIR:-}"
|
|
INIT="${INIT:-auto}"
|
|
TAG="${RGO_VERSION:-${VERSION:-latest}}"
|
|
METHOD="${RGO_METHOD:-}"
|
|
SERVICE="${RGO_SERVICE:-auto}"
|
|
GOAMD64_REQ="${RGO_GOAMD64:-auto}"
|
|
VERIFY="${RGO_VERIFY:-auto}"
|
|
DRY_RUN=0
|
|
ASSUME_YES=0
|
|
FORCE=0
|
|
PREFIX_SET=0
|
|
BINDIR_SET=0
|
|
SUDO=""
|
|
WORK_DIR=""
|
|
CHECKOUT_ROOT=""
|
|
|
|
OS=""
|
|
ARCH=""
|
|
GOAMD64="v1"
|
|
V3_OK=0
|
|
INIT_DETECTED="unknown"
|
|
ASSET=""
|
|
SRC_ROOT=""
|
|
|
|
log() {
|
|
printf 'install.sh: %s\n' "$*"
|
|
}
|
|
|
|
err() {
|
|
printf 'install.sh: %s\n' "$*" >&2
|
|
}
|
|
|
|
die() {
|
|
err "$*"
|
|
exit 1
|
|
}
|
|
|
|
usage() {
|
|
cat <<'EOF'
|
|
Usage: install.sh [options]
|
|
|
|
Install reticulum-go (release binary or build from source) and optional
|
|
init service files (systemd, OpenRC, runit, or dinit).
|
|
|
|
Options:
|
|
--binary Fetch a pre-built GitHub release binary
|
|
--source Build from source (needs Go 1.26.5 or later)
|
|
--dry-run, -n Print detections and planned actions, write nothing
|
|
--yes, -y Non-interactive defaults (binary, install service)
|
|
--no-service Skip init service files
|
|
--service Install service files for the detected init
|
|
--init TYPE auto|systemd|openrc|runit|dinit|all|none
|
|
--prefix DIR Install prefix (default /usr/local, or PREFIX)
|
|
--destdir DIR Staging root (default empty, or DESTDIR)
|
|
--bindir DIR Binary directory (default PREFIX/bin, or BINDIR)
|
|
--tag TAG Release tag (default latest, or RGO_VERSION)
|
|
--goamd64 LEVEL auto|v1|v3 (linux/amd64 microarchitecture)
|
|
--verify Require cosign attestation (needs cosign)
|
|
--no-verify Skip cosign even if it is installed
|
|
--force Allow a v3 binary on a CPU that does not report v3
|
|
-h, --help Show this help
|
|
|
|
Environment:
|
|
PREFIX DESTDIR BINDIR INIT VERSION RGO_VERSION RGO_REPO RGO_METHOD
|
|
RGO_SERVICE RGO_GOAMD64 RGO_VERIFY RGO_GITHUB RGO_GITHUB_API
|
|
|
|
Examples:
|
|
curl -fsSL https://raw.githubusercontent.com/Quad4-Software/Reticulum-Go/master/install.sh | sh
|
|
curl -fsSL https://raw.githubusercontent.com/Quad4-Software/Reticulum-Go/master/install.sh | sh -s -- --dry-run
|
|
./install.sh --source --init runit
|
|
EOF
|
|
}
|
|
|
|
cleanup() {
|
|
if [ -n "$WORK_DIR" ] && [ -d "$WORK_DIR" ]; then
|
|
rm -rf "$WORK_DIR"
|
|
fi
|
|
}
|
|
|
|
trap cleanup EXIT INT HUP TERM
|
|
|
|
is_checkout() {
|
|
root="$1"
|
|
[ -n "$root" ] || return 1
|
|
[ -f "$root/go.mod" ] || return 1
|
|
[ -d "$root/cmd/reticulum-go" ] || return 1
|
|
[ -d "$root/packaging" ] || return 1
|
|
}
|
|
|
|
resolve_checkout() {
|
|
script="$0"
|
|
case "$script" in
|
|
-|sh|dash|bash|ksh|*/sh|*/dash|*/bash)
|
|
return 1
|
|
;;
|
|
esac
|
|
if [ ! -f "$script" ]; then
|
|
return 1
|
|
fi
|
|
dir=$(CDPATH='' cd -- "$(dirname "$script")" && pwd)
|
|
if is_checkout "$dir"; then
|
|
CHECKOUT_ROOT="$dir"
|
|
return 0
|
|
fi
|
|
return 1
|
|
}
|
|
|
|
has_tty() {
|
|
[ -r /dev/tty ] && [ -w /dev/tty ]
|
|
}
|
|
|
|
prompt() {
|
|
msg="$1"
|
|
def="$2"
|
|
if [ "$ASSUME_YES" = 1 ] || [ "$DRY_RUN" = 1 ] || ! has_tty; then
|
|
printf '%s\n' "$def"
|
|
return 0
|
|
fi
|
|
printf '%s' "$msg" >/dev/tty
|
|
ans=""
|
|
read -r ans </dev/tty || ans="$def"
|
|
if [ -z "$ans" ]; then
|
|
ans="$def"
|
|
fi
|
|
printf '%s\n' "$ans"
|
|
}
|
|
|
|
need_cmd() {
|
|
command -v "$1" >/dev/null 2>&1 || die "need '$1' on PATH"
|
|
}
|
|
|
|
have_cmd() {
|
|
command -v "$1" >/dev/null 2>&1
|
|
}
|
|
|
|
make_work_dir() {
|
|
if [ -n "$WORK_DIR" ]; then
|
|
return 0
|
|
fi
|
|
if have_cmd mktemp; then
|
|
WORK_DIR=$(mktemp -d "${TMPDIR:-/tmp}/rgo-install.XXXXXX")
|
|
else
|
|
WORK_DIR="${TMPDIR:-/tmp}/rgo-install.$$"
|
|
mkdir -m 700 "$WORK_DIR"
|
|
fi
|
|
}
|
|
|
|
as_root() {
|
|
if [ "$DRY_RUN" = 1 ]; then
|
|
if [ -n "$SUDO" ]; then
|
|
printf 'install.sh: dry-run: %s' "$SUDO"
|
|
for a in "$@"; do
|
|
printf ' %s' "$a"
|
|
done
|
|
printf '\n'
|
|
else
|
|
printf 'install.sh: dry-run:'
|
|
for a in "$@"; do
|
|
printf ' %s' "$a"
|
|
done
|
|
printf '\n'
|
|
fi
|
|
return 0
|
|
fi
|
|
if [ -n "$SUDO" ]; then
|
|
sudo "$@"
|
|
else
|
|
"$@"
|
|
fi
|
|
}
|
|
|
|
http_get() {
|
|
url="$1"
|
|
dest="$2"
|
|
if have_cmd curl; then
|
|
curl -fL --proto '=https' --tlsv1.2 --retry 3 --retry-delay 1 -o "$dest" "$url" || return 1
|
|
elif have_cmd wget; then
|
|
wget -O "$dest" "$url" || return 1
|
|
else
|
|
die "need curl or wget to download $url"
|
|
fi
|
|
}
|
|
|
|
http_effective_url() {
|
|
url="$1"
|
|
if have_cmd curl; then
|
|
curl -fsSL --proto '=https' --tlsv1.2 -o /dev/null -w '%{url_effective}' "$url" || return 1
|
|
else
|
|
return 1
|
|
fi
|
|
}
|
|
|
|
flag_in() {
|
|
needle=$(LC_ALL=C printf '%s' "$1" | tr 'A-Z' 'a-z')
|
|
hay=$(LC_ALL=C printf '%s' "$2" | tr 'A-Z' 'a-z' | tr ',' ' ')
|
|
case " $hay " in
|
|
*" $needle "*) return 0 ;;
|
|
*) return 1 ;;
|
|
esac
|
|
}
|
|
|
|
cpu_is_goamd64_v3() {
|
|
flags="$1"
|
|
for f in avx avx2 bmi1 bmi2 fma movbe; do
|
|
flag_in "$f" "$flags" || return 1
|
|
done
|
|
if ! flag_in osxsave "$flags" && ! flag_in xsave "$flags"; then
|
|
return 1
|
|
fi
|
|
if flag_in lzcnt "$flags" || flag_in abm "$flags"; then
|
|
return 0
|
|
fi
|
|
return 1
|
|
}
|
|
|
|
linux_cpu_flags() {
|
|
if [ -r /proc/cpuinfo ]; then
|
|
awk '/^flags[[:space:]]*:/ { sub(/^[^:]*:[[:space:]]*/, ""); print; exit }' /proc/cpuinfo
|
|
fi
|
|
}
|
|
|
|
bsd_cpu_flags() {
|
|
if [ -r /var/run/dmesg.boot ]; then
|
|
awk '
|
|
BEGIN { out = "" }
|
|
/Features/ {
|
|
line = $0
|
|
gsub(/[<>(),=]/, " ", line)
|
|
out = out " " line
|
|
}
|
|
END { print out }
|
|
' /var/run/dmesg.boot
|
|
return 0
|
|
fi
|
|
if have_cmd dmesg; then
|
|
dmesg 2>/dev/null | awk '
|
|
BEGIN { out = "" }
|
|
/Features/ || /AVX/ {
|
|
line = $0
|
|
gsub(/[<>(),=]/, " ", line)
|
|
out = out " " line
|
|
}
|
|
END { print out }
|
|
'
|
|
fi
|
|
}
|
|
|
|
detect_v3() {
|
|
V3_OK=0
|
|
flags=""
|
|
case "$OS" in
|
|
linux)
|
|
flags=$(linux_cpu_flags)
|
|
;;
|
|
darwin)
|
|
feat=$(sysctl -n machdep.cpu.features 2>/dev/null || true)
|
|
leaf7=$(sysctl -n machdep.cpu.leaf7_features 2>/dev/null || true)
|
|
ext=$(sysctl -n machdep.cpu.extfeatures 2>/dev/null || true)
|
|
flags="$feat $leaf7 $ext"
|
|
;;
|
|
*)
|
|
flags=$(bsd_cpu_flags)
|
|
;;
|
|
esac
|
|
if [ "$ARCH" = amd64 ] && cpu_is_goamd64_v3 "$flags"; then
|
|
V3_OK=1
|
|
return 0
|
|
fi
|
|
return 1
|
|
}
|
|
|
|
detect_os() {
|
|
sys=$(uname -s)
|
|
case "$sys" in
|
|
Linux) OS=linux ;;
|
|
Darwin) OS=darwin ;;
|
|
FreeBSD) OS=freebsd ;;
|
|
OpenBSD) OS=openbsd ;;
|
|
NetBSD) OS=netbsd ;;
|
|
DragonFly) OS=dragonfly ;;
|
|
AIX) OS=aix ;;
|
|
SunOS)
|
|
if [ -f /etc/os-release ] && grep -q 'ID=illumos' /etc/os-release; then
|
|
OS=illumos
|
|
elif [ -f /etc/release ] && grep -qi illumos /etc/release; then
|
|
OS=illumos
|
|
else
|
|
OS=solaris
|
|
fi
|
|
;;
|
|
*)
|
|
die "unsupported OS: $sys"
|
|
;;
|
|
esac
|
|
}
|
|
|
|
detect_arch() {
|
|
machine=$(uname -m)
|
|
case "$machine" in
|
|
x86_64 | amd64) ARCH=amd64 ;;
|
|
i386 | i486 | i586 | i686 | i86pc) ARCH=386 ;;
|
|
aarch64 | arm64) ARCH=arm64 ;;
|
|
armv6* | armv7* | arm) ARCH=arm ;;
|
|
riscv64) ARCH=riscv64 ;;
|
|
ppc64le | powerpc64le) ARCH=ppc64le ;;
|
|
ppc64 | powerpc64) ARCH=ppc64 ;;
|
|
mips64el | mips64le) ARCH=mips64le ;;
|
|
mips64) ARCH=mips64 ;;
|
|
mipsel | mipsle) ARCH=mipsle ;;
|
|
mips) ARCH=mips ;;
|
|
s390x) ARCH=s390x ;;
|
|
*) die "unsupported architecture: $machine" ;;
|
|
esac
|
|
|
|
if [ "$OS" = darwin ] && [ "$(sysctl -n sysctl.proc_translated 2>/dev/null || echo 0)" = 1 ]; then
|
|
ARCH=arm64
|
|
fi
|
|
|
|
bits=$(getconf LONG_BIT 2>/dev/null || echo 64)
|
|
if [ "$bits" = 32 ]; then
|
|
case "$ARCH" in
|
|
amd64) ARCH=386 ;;
|
|
arm64) ARCH=arm ;;
|
|
esac
|
|
fi
|
|
}
|
|
|
|
detect_init() {
|
|
if [ -d /run/systemd/system ] && have_cmd systemctl; then
|
|
INIT_DETECTED=systemd
|
|
return
|
|
fi
|
|
if have_cmd rc-service || have_cmd openrc; then
|
|
INIT_DETECTED=openrc
|
|
return
|
|
fi
|
|
if [ -d /etc/sv ] || [ -d /var/service ] || have_cmd runsv; then
|
|
INIT_DETECTED=runit
|
|
return
|
|
fi
|
|
if have_cmd dinitctl || [ -d /etc/dinit.d ]; then
|
|
INIT_DETECTED=dinit
|
|
return
|
|
fi
|
|
INIT_DETECTED=unknown
|
|
}
|
|
|
|
asset_name() {
|
|
if [ "$OS" = linux ] && [ "$ARCH" = amd64 ]; then
|
|
if [ "$GOAMD64" = v3 ]; then
|
|
printf '%s\n' "reticulum-go-linux-amd64-v3"
|
|
return
|
|
fi
|
|
printf '%s\n' "reticulum-go-linux-amd64"
|
|
return
|
|
fi
|
|
if [ "$OS" = linux ] && [ "$ARCH" = 386 ]; then
|
|
printf '%s\n' "reticulum-go-linux-i686"
|
|
return
|
|
fi
|
|
printf '%s\n' "reticulum-go-${OS}-${ARCH}"
|
|
}
|
|
|
|
release_url() {
|
|
name="$1"
|
|
if [ "$TAG" = latest ]; then
|
|
printf '%s/%s/releases/latest/download/%s\n' "$GITHUB" "$REPO" "$name"
|
|
else
|
|
printf '%s/%s/releases/download/%s/%s\n' "$GITHUB" "$REPO" "$TAG" "$name"
|
|
fi
|
|
}
|
|
|
|
raw_url() {
|
|
ref="$1"
|
|
path="$2"
|
|
printf '%s/%s/%s/%s\n' "$GITHUB_RAW" "$REPO" "$ref" "$path"
|
|
}
|
|
|
|
archive_url() {
|
|
if [ "$TAG" = latest ] || [ "$TAG" = master ]; then
|
|
printf '%s/%s/archive/refs/heads/master.tar.gz\n' "$GITHUB" "$REPO"
|
|
else
|
|
printf '%s/%s/archive/refs/tags/%s.tar.gz\n' "$GITHUB" "$REPO" "$TAG"
|
|
fi
|
|
}
|
|
|
|
resolve_git_ref() {
|
|
if [ "$TAG" != latest ]; then
|
|
printf '%s\n' "$TAG"
|
|
return 0
|
|
fi
|
|
eff=$(http_effective_url "$GITHUB/$REPO/releases/latest" || true)
|
|
if [ -n "$eff" ]; then
|
|
printf '%s\n' "${eff##*/}"
|
|
return 0
|
|
fi
|
|
if have_cmd curl; then
|
|
json=$(curl -fsSL --proto '=https' --tlsv1.2 "$GITHUB_API/repos/$REPO/releases/latest" || true)
|
|
tag=$(printf '%s\n' "$json" | sed -n 's/.*"tag_name"[[:space:]]*:[[:space:]]*"\([^"]*\)".*/\1/p' | head -1)
|
|
if [ -n "$tag" ]; then
|
|
printf '%s\n' "$tag"
|
|
return 0
|
|
fi
|
|
fi
|
|
printf '%s\n' master
|
|
}
|
|
|
|
version_ge() {
|
|
awk -v h="$1" -v n="$2" 'BEGIN {
|
|
split(h, a, ".")
|
|
split(n, b, ".")
|
|
for (i = 1; i <= 3; i++) {
|
|
ai = a[i] + 0
|
|
bi = b[i] + 0
|
|
if (ai > bi) exit 0
|
|
if (ai < bi) exit 1
|
|
}
|
|
exit 0
|
|
}'
|
|
}
|
|
|
|
check_go() {
|
|
need_cmd go
|
|
have=$(go env GOVERSION 2>/dev/null || true)
|
|
have=${have#go}
|
|
have=$(printf '%s' "$have" | sed 's/[^0-9.].*//')
|
|
if [ -z "$have" ]; then
|
|
die "could not read go version"
|
|
fi
|
|
if ! version_ge "$have" "$MIN_GO"; then
|
|
die "Go $MIN_GO or later required (found $have)"
|
|
fi
|
|
log "go $have"
|
|
}
|
|
|
|
dir_writable() {
|
|
dir="$1"
|
|
if [ -d "$dir" ] && [ -w "$dir" ]; then
|
|
return 0
|
|
fi
|
|
parent=$(dirname "$dir")
|
|
if [ -d "$parent" ] && [ -w "$parent" ]; then
|
|
return 0
|
|
fi
|
|
return 1
|
|
}
|
|
|
|
resolve_privilege() {
|
|
target="${DESTDIR}${BINDIR}"
|
|
if dir_writable "$target" || dir_writable "$(dirname "$target")"; then
|
|
SUDO=""
|
|
return 0
|
|
fi
|
|
if [ "$(id -u)" -eq 0 ]; then
|
|
SUDO=""
|
|
return 0
|
|
fi
|
|
if have_cmd sudo; then
|
|
SUDO=sudo
|
|
return 0
|
|
fi
|
|
if [ "$PREFIX_SET" = 1 ]; then
|
|
die "cannot write to $target (need root, sudo, or a writable --prefix)"
|
|
fi
|
|
PREFIX="$HOME/.local"
|
|
BINDIR="${PREFIX}/bin"
|
|
log "no root and $target not writable, using prefix $PREFIX"
|
|
if ! dir_writable "$BINDIR" && ! dir_writable "$PREFIX"; then
|
|
die "cannot write to $BINDIR"
|
|
fi
|
|
SUDO=""
|
|
}
|
|
|
|
choose_method() {
|
|
if [ -n "$METHOD" ]; then
|
|
return 0
|
|
fi
|
|
if [ "$DRY_RUN" = 1 ] || [ "$ASSUME_YES" = 1 ] || ! has_tty; then
|
|
METHOD=binary
|
|
log "method=binary (default)"
|
|
return 0
|
|
fi
|
|
log "Install method"
|
|
log " 1) Fetch pre-built binary from GitHub releases"
|
|
log " 2) Build from source (Go $MIN_GO or later)"
|
|
choice=$(prompt "Select [1/2]: " "1")
|
|
case "$choice" in
|
|
2 | source | s)
|
|
METHOD=source
|
|
;;
|
|
*)
|
|
METHOD=binary
|
|
;;
|
|
esac
|
|
}
|
|
|
|
choose_service() {
|
|
if [ "$SERVICE" = yes ] || [ "$SERVICE" = no ]; then
|
|
return 0
|
|
fi
|
|
if [ "$INIT" = none ]; then
|
|
SERVICE=no
|
|
return 0
|
|
fi
|
|
if [ "$INIT" != auto ] && [ "$INIT" != none ]; then
|
|
SERVICE=yes
|
|
return 0
|
|
fi
|
|
if [ "$INIT_DETECTED" = unknown ]; then
|
|
SERVICE=no
|
|
log "no init system detected, skipping service"
|
|
return 0
|
|
fi
|
|
if [ "$DRY_RUN" = 1 ] || [ "$ASSUME_YES" = 1 ] || ! has_tty; then
|
|
SERVICE=yes
|
|
return 0
|
|
fi
|
|
ans=$(prompt "Install $INIT_DETECTED service files? [Y/n] " "Y")
|
|
case "$ans" in
|
|
n | N | no | NO)
|
|
SERVICE=no
|
|
;;
|
|
*)
|
|
SERVICE=yes
|
|
;;
|
|
esac
|
|
}
|
|
|
|
resolve_goamd64() {
|
|
GOAMD64=v1
|
|
if [ "$OS" != linux ] || [ "$ARCH" != amd64 ]; then
|
|
return 0
|
|
fi
|
|
case "$GOAMD64_REQ" in
|
|
v3)
|
|
if [ "$V3_OK" = 1 ] || [ "$FORCE" = 1 ]; then
|
|
GOAMD64=v3
|
|
else
|
|
die "CPU does not report GOAMD64=v3 flags (AVX2 BMI1 BMI2 FMA LZCNT MOVBE OSXSAVE). use --goamd64 v1 or --force"
|
|
fi
|
|
;;
|
|
v1)
|
|
GOAMD64=v1
|
|
;;
|
|
auto | "")
|
|
if [ "$V3_OK" = 1 ]; then
|
|
GOAMD64=v3
|
|
else
|
|
GOAMD64=v1
|
|
fi
|
|
;;
|
|
*)
|
|
die "unknown --goamd64 '$GOAMD64_REQ' (auto|v1|v3)"
|
|
;;
|
|
esac
|
|
}
|
|
|
|
install_file() {
|
|
mode="$1"
|
|
src="$2"
|
|
dest="$3"
|
|
as_root mkdir -p "$(dirname "$dest")"
|
|
if have_cmd install; then
|
|
as_root install -m "$mode" "$src" "$dest"
|
|
else
|
|
as_root cp "$src" "$dest"
|
|
as_root chmod "$mode" "$dest"
|
|
fi
|
|
}
|
|
|
|
install_binary_and_links() {
|
|
src="$1"
|
|
dest="${DESTDIR}${BINDIR}/${BINARY_NAME}"
|
|
log "install $dest"
|
|
as_root mkdir -p "${DESTDIR}${BINDIR}"
|
|
install_file 755 "$src" "$dest"
|
|
for name in $TOOL_LINKS; do
|
|
log "link ${DESTDIR}${BINDIR}/$name -> $BINARY_NAME"
|
|
as_root ln -sf "$BINARY_NAME" "${DESTDIR}${BINDIR}/$name"
|
|
done
|
|
}
|
|
|
|
install_man_from_tree() {
|
|
root="$1"
|
|
man_src="$root/man"
|
|
if [ ! -d "$man_src" ]; then
|
|
return 0
|
|
fi
|
|
mandir="${DESTDIR}${PREFIX}/share/man"
|
|
log "install man pages under $mandir"
|
|
as_root mkdir -p "$mandir/man1" "$mandir/man8"
|
|
for f in "$man_src"/*.1; do
|
|
[ -f "$f" ] || continue
|
|
base=$(basename "$f")
|
|
install_file 644 "$f" "$mandir/man1/$base"
|
|
done
|
|
if [ -f "$man_src/reticulum-go.8" ]; then
|
|
install_file 644 "$man_src/reticulum-go.8" "$mandir/man8/reticulum-go.8"
|
|
fi
|
|
as_root ln -sf reticulum-go-status.1 "$mandir/man1/rgostatus.1"
|
|
as_root ln -sf reticulum-go-id.1 "$mandir/man1/rgoid.1"
|
|
as_root ln -sf reticulum-go-probe.1 "$mandir/man1/rgoprobe.1"
|
|
as_root ln -sf reticulum-go-path.1 "$mandir/man1/rgopath.1"
|
|
as_root ln -sf reticulum-go-cp.1 "$mandir/man1/rgocp.1"
|
|
as_root ln -sf reticulum-go-x.1 "$mandir/man1/rgox.1"
|
|
as_root ln -sf reticulum-go-x.1 "$mandir/man1/rnx.1"
|
|
as_root ln -sf reticulum-go-sh.1 "$mandir/man1/rgosh.1"
|
|
as_root ln -sf reticulum-go-pageserver.1 "$mandir/man1/rgopageserver.1"
|
|
as_root ln -sf reticulum-go-slow.1 "$mandir/man1/rgoslow.1"
|
|
as_root ln -sf reticulum-go-speedtest.1 "$mandir/man1/rgospeed.1"
|
|
as_root ln -sf reticulum-go-dump.1 "$mandir/man1/rgodump.1"
|
|
as_root ln -sf reticulum-go-snapshot.1 "$mandir/man1/rgosnap.1"
|
|
as_root ln -sf reticulum-go-self-check.1 "$mandir/man1/rgoselfcheck.1"
|
|
}
|
|
|
|
fetch_packaging_tree() {
|
|
dest="$1"
|
|
ref="$2"
|
|
log "fetch packaging files (ref $ref)"
|
|
set -- \
|
|
scripts/install-service.sh \
|
|
packaging/reticulum-go.config \
|
|
packaging/systemd/reticulum-go.service \
|
|
packaging/systemd/user.conf.example \
|
|
packaging/openrc/reticulum-go \
|
|
packaging/runit/reticulum-go/run \
|
|
packaging/runit/reticulum-go/log/run \
|
|
packaging/dinit/reticulum-go
|
|
for rel in "$@"; do
|
|
out="$dest/$rel"
|
|
mkdir -p "$(dirname "$out")"
|
|
http_get "$(raw_url "$ref" "$rel")" "$out"
|
|
done
|
|
chmod 755 "$dest/scripts/install-service.sh"
|
|
}
|
|
|
|
run_install_service() {
|
|
tree="$1"
|
|
init_type="$2"
|
|
if [ ! -f "$tree/scripts/install-service.sh" ]; then
|
|
die "missing $tree/scripts/install-service.sh"
|
|
fi
|
|
if [ -n "$DESTDIR" ]; then
|
|
as_root sh "$tree/scripts/install-service.sh" --prefix "$PREFIX" --destdir "$DESTDIR" --bindir "$BINDIR" --init "$init_type"
|
|
else
|
|
as_root sh "$tree/scripts/install-service.sh" --prefix "$PREFIX" --bindir "$BINDIR" --init "$init_type"
|
|
fi
|
|
}
|
|
|
|
verify_blob() {
|
|
blob="$1"
|
|
bundle="$2"
|
|
key="$3"
|
|
if ! have_cmd cosign; then
|
|
return 1
|
|
fi
|
|
cosign verify-blob-attestation \
|
|
--private-infrastructure \
|
|
--key "$key" \
|
|
--bundle "$bundle" \
|
|
--type slsaprovenance1 \
|
|
"$blob"
|
|
}
|
|
|
|
maybe_verify() {
|
|
blob="$1"
|
|
name="$2"
|
|
if [ "$VERIFY" = no ]; then
|
|
log "cosign verification skipped (--no-verify)"
|
|
return 0
|
|
fi
|
|
if [ "$VERIFY" = yes ] && ! have_cmd cosign; then
|
|
die "cosign is required for --verify"
|
|
fi
|
|
if ! have_cmd cosign; then
|
|
log "cosign not found, skipping attestation check (install cosign or pass --verify)"
|
|
return 0
|
|
fi
|
|
make_work_dir
|
|
bundle="$WORK_DIR/${name}.cosign.bundle"
|
|
key="$WORK_DIR/cosign.pub"
|
|
ref=$(resolve_git_ref)
|
|
if ! http_get "$(release_url "${name}.cosign.bundle")" "$bundle"; then
|
|
if [ "$VERIFY" = yes ]; then
|
|
die "failed to download ${name}.cosign.bundle"
|
|
fi
|
|
log "no cosign bundle for $name, skipping verification"
|
|
return 0
|
|
fi
|
|
if [ -n "$CHECKOUT_ROOT" ] && [ -f "$CHECKOUT_ROOT/cosign.pub" ]; then
|
|
cp "$CHECKOUT_ROOT/cosign.pub" "$key"
|
|
elif ! http_get "$(raw_url "$ref" cosign.pub)" "$key"; then
|
|
if [ "$VERIFY" = yes ]; then
|
|
die "failed to download cosign.pub"
|
|
fi
|
|
log "could not fetch cosign.pub, skipping verification"
|
|
return 0
|
|
fi
|
|
log "verify cosign attestation for $name"
|
|
if ! verify_blob "$blob" "$bundle" "$key"; then
|
|
die "cosign verification failed for $name"
|
|
fi
|
|
log "cosign attestation ok"
|
|
}
|
|
|
|
build_from_tree() {
|
|
root="$1"
|
|
out="$2"
|
|
(
|
|
cd "$root"
|
|
export CGO_ENABLED=0
|
|
export GOFLAGS="${GOFLAGS:--mod=vendor}"
|
|
if [ "$OS" = linux ] && [ "$ARCH" = amd64 ]; then
|
|
export GOAMD64
|
|
fi
|
|
ldflags="-s -w"
|
|
if [ "$TAG" != latest ] && [ "$TAG" != master ]; then
|
|
ldflags="$ldflags -X main.defaultVersion=${TAG}"
|
|
elif [ -n "$CHECKOUT_ROOT" ] && have_cmd git; then
|
|
ver=$(git -C "$root" describe --tags --always --dirty 2>/dev/null || true)
|
|
if [ -n "$ver" ]; then
|
|
ldflags="$ldflags -X main.defaultVersion=${ver}"
|
|
fi
|
|
fi
|
|
log "go build (GOAMD64=${GOAMD64:-default}) -> $out"
|
|
go build -ldflags "$ldflags" -o "$out" ./cmd/reticulum-go
|
|
)
|
|
}
|
|
|
|
fetch_source_tree() {
|
|
make_work_dir
|
|
tarball="$WORK_DIR/src.tar.gz"
|
|
url=$(archive_url)
|
|
log "download source $url"
|
|
http_get "$url" "$tarball"
|
|
mkdir -p "$WORK_DIR/src"
|
|
if have_cmd gzip; then
|
|
gzip -dc "$tarball" | tar -C "$WORK_DIR/src" -xf -
|
|
else
|
|
tar -C "$WORK_DIR/src" -xzf "$tarball"
|
|
fi
|
|
for d in "$WORK_DIR/src"/*; do
|
|
if [ -d "$d" ]; then
|
|
SRC_ROOT="$d"
|
|
return 0
|
|
fi
|
|
done
|
|
die "source archive had no top-level directory"
|
|
}
|
|
|
|
print_plan() {
|
|
log "os=$OS arch=$ARCH goamd64=$GOAMD64 v3_ok=$V3_OK"
|
|
log "init_detected=$INIT_DETECTED init=$INIT service=$SERVICE"
|
|
log "method=$METHOD tag=$TAG"
|
|
log "prefix=$PREFIX bindir=$BINDIR destdir=${DESTDIR:-none}"
|
|
if [ -n "$CHECKOUT_ROOT" ]; then
|
|
log "checkout=$CHECKOUT_ROOT"
|
|
fi
|
|
if [ "$METHOD" = binary ]; then
|
|
log "asset=$ASSET"
|
|
log "url=$(release_url "$ASSET")"
|
|
else
|
|
if [ -n "$CHECKOUT_ROOT" ]; then
|
|
log "source=checkout $CHECKOUT_ROOT"
|
|
else
|
|
log "source=$(archive_url)"
|
|
fi
|
|
fi
|
|
if [ "$DRY_RUN" = 1 ]; then
|
|
log "dry-run: no files will be written"
|
|
log "would install ${DESTDIR}${BINDIR}/${BINARY_NAME}"
|
|
log "would install tool links: $TOOL_LINKS"
|
|
if [ "$METHOD" = source ] || [ -n "$CHECKOUT_ROOT" ]; then
|
|
log "would install man pages under ${DESTDIR}${PREFIX}/share/man"
|
|
fi
|
|
if [ "$SERVICE" = yes ]; then
|
|
svc="$INIT"
|
|
if [ "$svc" = auto ]; then
|
|
svc="$INIT_DETECTED"
|
|
fi
|
|
log "would install $svc service files (state dir $STATE_DIR)"
|
|
else
|
|
log "would skip service files"
|
|
fi
|
|
if [ -n "$SUDO" ]; then
|
|
log "would use sudo for privileged paths"
|
|
fi
|
|
fi
|
|
}
|
|
|
|
install_from_binary() {
|
|
need_cmd uname
|
|
if ! have_cmd curl && ! have_cmd wget; then
|
|
die "need curl or wget to fetch a release binary"
|
|
fi
|
|
make_work_dir
|
|
bin="$WORK_DIR/$ASSET"
|
|
url=$(release_url "$ASSET")
|
|
log "download $url"
|
|
if ! http_get "$url" "$bin"; then
|
|
if [ "$OS" = linux ] && [ "$ARCH" = amd64 ] && [ "$GOAMD64" = v3 ]; then
|
|
log "v3 asset missing, falling back to GOAMD64=v1"
|
|
GOAMD64=v1
|
|
ASSET=$(asset_name)
|
|
bin="$WORK_DIR/$ASSET"
|
|
url=$(release_url "$ASSET")
|
|
log "download $url"
|
|
http_get "$url" "$bin"
|
|
else
|
|
die "failed to download $url"
|
|
fi
|
|
fi
|
|
chmod 755 "$bin"
|
|
maybe_verify "$bin" "$ASSET"
|
|
install_binary_and_links "$bin"
|
|
if [ -n "$CHECKOUT_ROOT" ]; then
|
|
install_man_from_tree "$CHECKOUT_ROOT"
|
|
fi
|
|
if [ "$SERVICE" = yes ]; then
|
|
svc="$INIT"
|
|
if [ "$svc" = auto ]; then
|
|
svc="$INIT_DETECTED"
|
|
fi
|
|
if [ -n "$CHECKOUT_ROOT" ]; then
|
|
run_install_service "$CHECKOUT_ROOT" "$svc"
|
|
else
|
|
ref=$(resolve_git_ref)
|
|
pkg="$WORK_DIR/tree"
|
|
fetch_packaging_tree "$pkg" "$ref"
|
|
run_install_service "$pkg" "$svc"
|
|
fi
|
|
fi
|
|
}
|
|
|
|
install_from_source() {
|
|
check_go
|
|
need_cmd tar
|
|
if [ -n "$CHECKOUT_ROOT" ]; then
|
|
SRC_ROOT="$CHECKOUT_ROOT"
|
|
else
|
|
if ! have_cmd curl && ! have_cmd wget; then
|
|
die "need curl or wget to fetch source"
|
|
fi
|
|
fetch_source_tree
|
|
fi
|
|
make_work_dir
|
|
out="$WORK_DIR/$BINARY_NAME"
|
|
build_from_tree "$SRC_ROOT" "$out"
|
|
install_binary_and_links "$out"
|
|
install_man_from_tree "$SRC_ROOT"
|
|
if [ "$SERVICE" = yes ]; then
|
|
svc="$INIT"
|
|
if [ "$svc" = auto ]; then
|
|
svc="$INIT_DETECTED"
|
|
fi
|
|
run_install_service "$SRC_ROOT" "$svc"
|
|
fi
|
|
}
|
|
|
|
parse_args() {
|
|
while [ $# -gt 0 ]; do
|
|
case "$1" in
|
|
--binary)
|
|
METHOD=binary
|
|
shift
|
|
;;
|
|
--source)
|
|
METHOD=source
|
|
shift
|
|
;;
|
|
--dry-run | -n)
|
|
DRY_RUN=1
|
|
shift
|
|
;;
|
|
--yes | -y)
|
|
ASSUME_YES=1
|
|
shift
|
|
;;
|
|
--no-service)
|
|
SERVICE=no
|
|
INIT=none
|
|
shift
|
|
;;
|
|
--service)
|
|
SERVICE=yes
|
|
shift
|
|
;;
|
|
--init)
|
|
[ $# -ge 2 ] || die "option $1 needs a value"
|
|
INIT="$2"
|
|
shift 2
|
|
;;
|
|
--init=*)
|
|
INIT="${1#--init=}"
|
|
shift
|
|
;;
|
|
--prefix)
|
|
[ $# -ge 2 ] || die "option $1 needs a value"
|
|
PREFIX="$2"
|
|
PREFIX_SET=1
|
|
shift 2
|
|
;;
|
|
--destdir)
|
|
[ $# -ge 2 ] || die "option $1 needs a value"
|
|
DESTDIR="$2"
|
|
shift 2
|
|
;;
|
|
--bindir)
|
|
[ $# -ge 2 ] || die "option $1 needs a value"
|
|
BINDIR="$2"
|
|
BINDIR_SET=1
|
|
shift 2
|
|
;;
|
|
--tag | --version)
|
|
[ $# -ge 2 ] || die "option $1 needs a value"
|
|
TAG="$2"
|
|
shift 2
|
|
;;
|
|
--tag=* | --version=*)
|
|
TAG="${1#*=}"
|
|
shift
|
|
;;
|
|
--goamd64)
|
|
[ $# -ge 2 ] || die "option $1 needs a value"
|
|
GOAMD64_REQ="$2"
|
|
shift 2
|
|
;;
|
|
--goamd64=*)
|
|
GOAMD64_REQ="${1#--goamd64=}"
|
|
shift
|
|
;;
|
|
--verify)
|
|
VERIFY=yes
|
|
shift
|
|
;;
|
|
--no-verify)
|
|
VERIFY=no
|
|
shift
|
|
;;
|
|
--force)
|
|
FORCE=1
|
|
shift
|
|
;;
|
|
-h | --help)
|
|
usage
|
|
exit 0
|
|
;;
|
|
--)
|
|
shift
|
|
break
|
|
;;
|
|
-*)
|
|
err "unknown option: $1"
|
|
usage >&2
|
|
exit 1
|
|
;;
|
|
*)
|
|
err "unexpected argument: $1"
|
|
usage >&2
|
|
exit 1
|
|
;;
|
|
esac
|
|
done
|
|
}
|
|
|
|
main() {
|
|
parse_args "$@"
|
|
|
|
case "$INIT" in
|
|
auto | systemd | openrc | runit | dinit | all | none) ;;
|
|
*) die "unknown --init '$INIT' (auto|systemd|openrc|runit|dinit|all|none)" ;;
|
|
esac
|
|
|
|
case "$METHOD" in
|
|
"" | binary | source) ;;
|
|
*) die "unknown method '$METHOD' (binary|source)" ;;
|
|
esac
|
|
|
|
if [ "$BINDIR_SET" = 0 ] || [ -z "$BINDIR" ]; then
|
|
BINDIR="${PREFIX}/bin"
|
|
fi
|
|
|
|
resolve_checkout || true
|
|
detect_os
|
|
detect_arch
|
|
detect_v3 || true
|
|
detect_init
|
|
resolve_goamd64
|
|
ASSET=$(asset_name)
|
|
|
|
if [ "$DRY_RUN" = 0 ]; then
|
|
resolve_privilege
|
|
else
|
|
target="${DESTDIR}${BINDIR}"
|
|
if ! dir_writable "$target" && ! dir_writable "$(dirname "$target")" && [ "$(id -u)" -ne 0 ]; then
|
|
if have_cmd sudo; then
|
|
SUDO=sudo
|
|
fi
|
|
fi
|
|
fi
|
|
|
|
choose_method
|
|
choose_service
|
|
|
|
if [ "$SERVICE" = yes ] && [ "$INIT" = auto ] && [ "$INIT_DETECTED" = unknown ]; then
|
|
if [ "$ASSUME_YES" = 1 ] || [ "$DRY_RUN" = 1 ]; then
|
|
SERVICE=no
|
|
log "no init system detected, skipping service"
|
|
else
|
|
die "could not detect init system (set --init systemd|openrc|runit|dinit|all or --no-service)"
|
|
fi
|
|
fi
|
|
|
|
print_plan
|
|
|
|
if [ "$DRY_RUN" = 1 ]; then
|
|
return 0
|
|
fi
|
|
|
|
case "$METHOD" in
|
|
binary) install_from_binary ;;
|
|
source) install_from_source ;;
|
|
*) die "internal error: method=$METHOD" ;;
|
|
esac
|
|
|
|
log "installed ${DESTDIR}${BINDIR}/${BINARY_NAME}"
|
|
if have_cmd "${DESTDIR}${BINDIR}/${BINARY_NAME}" || [ -x "${DESTDIR}${BINDIR}/${BINARY_NAME}" ]; then
|
|
if [ -z "$DESTDIR" ]; then
|
|
"${BINDIR}/${BINARY_NAME}" --version 2>/dev/null || true
|
|
fi
|
|
fi
|
|
log "done"
|
|
}
|
|
|
|
main "$@"
|