Add installer
Release rolling / release (push) Canceled after 5m29s

This commit is contained in:
Raven Scott
2026-07-31 12:02:56 -04:00
parent cdf776a68f
commit 6105b8d4ac
6 changed files with 713 additions and 2 deletions
+624
View File
@@ -0,0 +1,624 @@
#!/usr/bin/env bash
# bare-os interactive installer (peardock-style curl|bash)
#
# One-liner (Gitea raw — always works):
# curl -fsSL https://git.ssh.surf/snxraven/bare-operating-system/raw/branch/main/scripts/install.sh | bash
#
# Canonical install host (when DNS/proxy serves this script):
# curl -fsSL https://install.bare-os.boats | bash
#
# Non-interactive:
# curl -fsSL …/scripts/install.sh | bash -s -- --seeder --yes
# curl -fsSL …/scripts/install.sh | bash -s -- --booter --yes
# curl -fsSL …/scripts/install.sh | bash -s -- --both --yes
# BARE_OS_ROLE=both bash scripts/install.sh --yes --system
#
# Env overrides:
# BARE_OS_ROLE=seeder|booter|both
# BARE_OS_VERSION / BARE_OS_TAG=rolling
# BARE_OS_GITEA_URL=https://git.ssh.surf
# BARE_OS_OWNER=snxraven BARE_OS_REPO=bare-operating-system
# BARE_OS_DIR=… (default: ~/.local/share/bare-os, or /opt/bare-os with --system)
# BARE_OS_YES=1
# BARE_OS_SYSTEM=1 — Linux systemd under /opt/bare-os
# BARE_OS_NO_UPDATES=1 — pass --no-updates to installed binaries / units
#
set -euo pipefail
# ─── defaults ───────────────────────────────────────────────────────────────
GITEA_URL="${BARE_OS_GITEA_URL:-https://git.ssh.surf}"
OWNER="${BARE_OS_OWNER:-snxraven}"
REPO="${BARE_OS_REPO:-bare-operating-system}"
TAG="${BARE_OS_TAG:-rolling}"
VERSION="${BARE_OS_VERSION:-}"
ROLE="${BARE_OS_ROLE:-}"
ASSUME_YES="${BARE_OS_YES:-0}"
SYSTEM_INSTALL="${BARE_OS_SYSTEM:-0}"
NO_UPDATES="${BARE_OS_NO_UPDATES:-0}"
INSTALL_DIR="${BARE_OS_DIR:-}"
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[bare-os]%s %s\n' "$C_TEAL" "$C_RESET" "$*"; }
ok() { printf '%s[bare-os]%s %s✓%s %s\n' "$C_TEAL" "$C_RESET" "$C_GREEN" "$C_RESET" "$*"; }
warn() { printf '%s[bare-os]%s %s!%s %s\n' "$C_TEAL" "$C_RESET" "$C_YELLOW" "$C_RESET" "$*" >&2; }
err() { printf '%s[bare-os]%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} ╔═══════════════════════════════════════╗
║ b a r e - o s ║
║ P2P system image · seeder + booter ║
╚═══════════════════════════════════════╝${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
--seeder|-s) ROLE=seeder; shift ;;
--booter|-b) ROLE=booter; shift ;;
--both) ROLE=both; shift ;;
--yes|-y) ASSUME_YES=1; shift ;;
--system) SYSTEM_INSTALL=1; shift ;;
--no-updates) NO_UPDATES=1; shift ;;
--tag) TAG="${2:-}"; shift 2 ;;
--version) VERSION="${2:-}"; shift 2 ;;
--dir) INSTALL_DIR="${2:-}"; shift 2 ;;
--help|-h)
cat <<EOF
Usage: install.sh [options]
--seeder, -s Install bare-os-seeder (+ kernel/)
--booter, -b Install bare-os-booter
--both Install seeder and booter
--yes, -y Non-interactive defaults
--system Linux: install under /opt/bare-os + systemd units
--no-updates Disable pear-runtime OTA on installed binaries/units
--tag TAG Release tag (default: rolling)
--version VER Pin asset version (default: auto from release)
--dir DIR Install root (default: ~/.local/share/bare-os)
--help This help
One-liner:
curl -fsSL https://git.ssh.surf/snxraven/bare-operating-system/raw/branch/main/scripts/install.sh | bash
curl -fsSL https://install.bare-os.boats | bash
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 ─────────────────────────────────────────────────────────────
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 helpers ───────────────────────────────────────────────────────
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 '^bare-os-(seeder|booter)-' || 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 "^bare-os-seeder-[0-9]" | head -1 || true)"
if [[ -z "$name" ]]; then
name="$(printf '%s\n' "$assets" | grep -E "^bare-os-booter-[0-9]" | head -1 || true)"
fi
if [[ -n "$name" ]]; then
echo "$name" | sed -E 's/^bare-os-(seeder|booter)-([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 BARE_OS_VERSION=x.y.z"
}
asset_url() {
local name="$1"
echo "${GITEA_URL}/${OWNER}/${REPO}/releases/download/${TAG}/${name}"
}
default_install_dir() {
if [[ -n "$INSTALL_DIR" ]]; then
echo "$INSTALL_DIR"
return
fi
if [[ "$SYSTEM_INSTALL" == "1" ]]; then
echo "/opt/bare-os"
else
echo "${HOME}/.local/share/bare-os"
fi
}
bin_extra_args() {
if [[ "$NO_UPDATES" == "1" ]]; then
echo "--no-updates"
else
echo ""
fi
}
ensure_path_shim() {
local name="$1" target="$2"
local bindir="$HOME/.local/bin"
mkdir -p "$bindir"
ln -sfn "$target" "${bindir}/${name}"
ok "Launcher: ${bindir}/${name}"
case ":$PATH:" in
*":${bindir}:"*) ;;
*) warn "Add ${bindir} to PATH (e.g. export PATH=\"\$HOME/.local/bin:\$PATH\")" ;;
esac
}
verify_checksum() {
local work="$1" tarball="$2" url="$3"
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
}
# ─── product install ────────────────────────────────────────────────────────
install_product() {
local product="$1" host="$2" version="$3" root="$4"
local archive url work tarball extract bin_name bin_src dest dest_bin
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
bin_name="bare-os-${product}"
archive="${bin_name}-${version}-${host}.tar.gz"
url="$(asset_url "$archive")"
work="${INSTALL_TMP}/${product}"
mkdir -p "$work"
tarball="${work}/${archive}"
download "$url" "$tarball"
verify_checksum "$work" "$tarball" "$url"
extract="${work}/extract"
rm -rf "$extract"
mkdir -p "$extract"
tar -xzf "$tarball" -C "$extract"
bin_src="$(find "$extract" -type f \( -name "$bin_name" -o -name "${bin_name}.exe" \) | head -1 || true)"
[[ -n "$bin_src" ]] || die "${bin_name} binary not found in archive"
chmod +x "$bin_src"
dest="${root}/${product}"
log "Installing ${product}${dest}"
if [[ "$SYSTEM_INSTALL" == "1" ]]; then
have_sudo || die "System install needs root/sudo"
run_root mkdir -p "$dest"
run_root install -m 0755 "$bin_src" "${dest}/${bin_name}"
if [[ "$product" == "seeder" ]]; then
local ksrc
ksrc="$(dirname "$bin_src")/kernel"
[[ -d "$ksrc" ]] || die "seeder archive missing kernel/ next to binary"
run_root rm -rf "${dest}/kernel"
run_root cp -a "$ksrc" "${dest}/kernel"
fi
if [[ "$(id -u)" -eq 0 ]]; then
:
else
# ownership for system tree
if id bare-os >/dev/null 2>&1; then
run_root chown -R bare-os:bare-os "$dest" 2>/dev/null || true
fi
fi
else
mkdir -p "$dest"
install -m 0755 "$bin_src" "${dest}/${bin_name}"
if [[ "$product" == "seeder" ]]; then
local ksrc
ksrc="$(dirname "$bin_src")/kernel"
[[ -d "$ksrc" ]] || die "seeder archive missing kernel/ next to binary"
rm -rf "${dest}/kernel"
cp -a "$ksrc" "${dest}/kernel"
fi
fi
dest_bin="${dest}/${bin_name}"
ok "Installed ${dest_bin}"
if [[ "$product" == "seeder" ]]; then
ok "Kernel tree: ${dest}/kernel"
fi
if [[ "$SYSTEM_INSTALL" != "1" ]]; then
ensure_path_shim "$bin_name" "$dest_bin"
fi
}
write_systemd_unit() {
local product="$1" root="$2"
local unit_name="bare-os-${product}"
local unit_path="/etc/systemd/system/${unit_name}.service"
local bin="${root}/${product}/bare-os-${product}"
local extra
extra="$(bin_extra_args)"
local exec_line="${bin}"
if [[ -n "${extra// }" ]]; then
exec_line="${bin} ${extra}"
fi
log "Writing ${unit_path}"
run_root tee "$unit_path" >/dev/null <<EOF
[Unit]
Description=Bare OS ${product}
Documentation=${GITEA_URL}/${OWNER}/${REPO}
After=network-online.target
Wants=network-online.target
[Service]
Type=simple
WorkingDirectory=${root}/${product}
ExecStart=${exec_line}
Restart=on-failure
RestartSec=5
TimeoutStartSec=30
TimeoutStopSec=30
User=bare-os
Group=bare-os
NoNewPrivileges=true
PrivateTmp=true
Environment=BARE_OS_HOST_DATA=${root}/data
EnvironmentFile=-${root}/${product}/.env
ReadWritePaths=${root}
[Install]
WantedBy=multi-user.target
EOF
}
ensure_system_user() {
local home="$1"
if id bare-os >/dev/null 2>&1; then
return 0
fi
log "Creating system user bare-os…"
if command -v useradd >/dev/null 2>&1; then
run_root useradd --system --home "$home" --shell /usr/sbin/nologin bare-os 2>/dev/null \
|| run_root useradd --system --home "$home" --shell /bin/false bare-os
elif command -v adduser >/dev/null 2>&1; then
run_root adduser --system --home "$home" --shell /usr/sbin/nologin --no-create-home bare-os || true
else
die "Cannot create bare-os user (need useradd or adduser)"
fi
id bare-os >/dev/null 2>&1 || die "bare-os user was not created"
}
install_systemd_for_role() {
local role="$1" root="$2" host="$3"
local os="${host%%-*}"
[[ "$SYSTEM_INSTALL" == "1" ]] || return 0
[[ "$os" == "linux" ]] || {
warn "--system is Linux-only; binaries installed under ${root} without systemd"
return 0
}
have_sudo || die "systemd install needs root/sudo"
ensure_system_user "$root"
run_root mkdir -p "${root}/data"
run_root chown -R bare-os:bare-os "$root" 2>/dev/null || run_root chown -R bare-os "$root"
case "$role" in
seeder)
write_systemd_unit seeder "$root"
;;
booter)
write_systemd_unit booter "$root"
;;
both)
write_systemd_unit seeder "$root"
write_systemd_unit booter "$root"
;;
esac
if command -v systemctl >/dev/null 2>&1; then
run_root systemctl daemon-reload
if confirm "Enable and start bare-os systemd unit(s) now?" "y"; then
case "$role" in
seeder) run_root systemctl enable --now bare-os-seeder.service ;;
booter) run_root systemctl enable --now bare-os-booter.service ;;
both)
run_root systemctl enable --now bare-os-seeder.service
run_root systemctl enable --now bare-os-booter.service
;;
esac
ok "systemd unit(s) enabled"
log "Logs: journalctl -u bare-os-seeder -f / journalctl -u bare-os-booter -f"
else
ok "Units installed. Start later with: sudo systemctl enable --now bare-os-seeder"
fi
else
warn "systemctl not found — binaries installed under ${root}"
fi
}
print_summary() {
local role="$1" root="$2"
local extra
extra="$(bin_extra_args)"
cat <<EOF
${C_BOLD}Installed (${role})${C_RESET}
Root: ${root}
EOF
case "$role" in
seeder|both)
cat <<EOF
Seeder: ${root}/seeder/bare-os-seeder
${root}/seeder/kernel/
EOF
;;
esac
case "$role" in
booter|both)
cat <<EOF
Booter: ${root}/booter/bare-os-booter
EOF
;;
esac
if [[ "$SYSTEM_INSTALL" != "1" ]]; then
cat <<EOF
Run (user install):
bare-os-seeder ${extra}
bare-os-booter ${extra}
Typical flow: start seeder first, then booter in a second terminal.
EOF
else
cat <<EOF
systemd:
sudo systemctl status bare-os-seeder bare-os-booter
sudo journalctl -u bare-os-seeder -f
EOF
fi
cat <<EOF
Docs: ${GITEA_URL}/${OWNER}/${REPO}
Releases: ${GITEA_URL}/${OWNER}/${REPO}/releases/tag/${TAG}
EOF
}
# ─── 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 version root
host="$(host_triple)"
os="${host%%-*}"
log "Detected host: ${C_BOLD}${host}${C_RESET} ($(uname -s) $(uname -m))"
log "Release: ${GITEA_URL}/${OWNER}/${REPO} @ ${TAG}"
if [[ "$SYSTEM_INSTALL" == "1" && "$os" != "linux" ]]; then
warn "--system ignored on ${os} (user install)"
SYSTEM_INSTALL=0
fi
version="$(resolve_version)"
log "Version: ${version}"
if [[ -z "$ROLE" ]]; then
echo "What do you want to install?"
echo " ${C_BOLD}1${C_RESET}) Seeder — publish system Hyperdrive + MBR"
echo " ${C_BOLD}2${C_RESET}) Booter — replicate image and run /boot/init.js"
echo " ${C_BOLD}3${C_RESET}) Both — recommended for a full node"
echo
local choice
choice="$(ask_choice "Choose 1/2/3" "3")"
case "$choice" in
1|s|seeder) ROLE=seeder ;;
2|b|booter) ROLE=booter ;;
3|both) ROLE=both ;;
*) die "Invalid choice: $choice" ;;
esac
fi
if [[ "$os" == "linux" && "$SYSTEM_INSTALL" != "1" && "$ASSUME_YES" != "1" ]]; then
if confirm "Install as Linux systemd service under /opt/bare-os?" "n"; then
SYSTEM_INSTALL=1
fi
fi
root="$(default_install_dir)"
log "Install root: ${root}"
INSTALL_TMP="$(mktemp -d "${TMPDIR_ROOT}/bare-os-install.XXXXXX")"
case "$ROLE" in
seeder)
install_product seeder "$host" "$version" "$root"
;;
booter)
install_product booter "$host" "$version" "$root"
;;
both)
install_product seeder "$host" "$version" "$root"
install_product booter "$host" "$version" "$root"
;;
*)
die "Unknown role: $ROLE (use seeder|booter|both)"
;;
esac
install_systemd_for_role "$ROLE" "$root" "$host"
print_summary "$ROLE" "$root"
ok "All done."
}
main "$@"