From e75fa0d75902f49e56910be0559fd8d91ea17395 Mon Sep 17 00:00:00 2001 From: Mitchell-Johnson Date: Sat, 19 Sep 2026 10:56:42 -0700 Subject: [PATCH] =?UTF-8?q?install.sh:=20an=20interactive=20installer=20?= =?UTF-8?q?=E2=80=94=20user,=20address,=20HTTPS,=20firewall,=20password=20?= =?UTF-8?q?=E2=80=94=20that=20still=20runs=20unattended;=20make=20publish?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Fable 5.1 --- Makefile | 14 +- install.sh | 367 +++++++++++++++++++++++++++++++++++++++++++++++------ 2 files changed, 338 insertions(+), 43 deletions(-) diff --git a/Makefile b/Makefile index 7e8c652..328d0b1 100644 --- a/Makefile +++ b/Makefile @@ -9,7 +9,7 @@ LDFLAGS = -s -w -X main.version=$(VERSION) # static, no cgo: the same file runs on glibc, musl (Alpine) and anything else with a Linux kernel GOBUILD = CGO_ENABLED=0 GOOS=linux GOARM=6 $(GO) build -trimpath -ldflags "$(LDFLAGS)" -.PHONY: all web build go dev cross release deploy push-nodes push-nodes-force test clean +.PHONY: all web build go dev cross release publish deploy push-nodes push-nodes-force test clean all: build @@ -42,6 +42,7 @@ release: web @for a in $(ARCHES); do \ echo "build linux/$$a"; GOARCH=$$a $(GOBUILD) -o dist/hamlab-linux-$$a ./cmd/hamlab || exit 1; done sed 's|@VERSION@|$(VERSION)|' install.sh > dist/install.sh && chmod +x dist/install.sh + echo $(VERSION) > dist/VERSION cd dist && sha256sum hamlab-linux-* install.sh > SHA256SUMS test: @@ -63,3 +64,14 @@ push-nodes-force: clean: rm -rf bin dist internal/web/dist web/dist + +# Put the release where `curl -fsSL https://hamlab-demo.bigham.dev/install.sh | sh` finds it: +# the static site beside the demo page (nginx, public through NPM). Binaries first, VERSION +# last, so nobody is ever pointed at files that aren't there yet. +PUBLISH ?= /home/core/apps/hamlab-demo/site +publish: release + mkdir -p $(PUBLISH)/dl + cp dist/hamlab-linux-* dist/SHA256SUMS $(PUBLISH)/dl/ + cp dist/install.sh $(PUBLISH)/dl/install.sh && cp dist/install.sh $(PUBLISH)/install.sh + cp dist/VERSION $(PUBLISH)/dl/VERSION + @echo "published $(VERSION): curl -fsSL https://hamlab-demo.bigham.dev/install.sh | sh" diff --git a/install.sh b/install.sh index dc636fd..298c271 100755 --- a/install.sh +++ b/install.sh @@ -1,77 +1,360 @@ #!/bin/sh -# Ham Lab installer: fetches the build for this machine and hands over to `hamlab install`, -# which sets up the binary, config, login password and a boot-time service. +# Ham Lab installer. # -# curl -fsSL "$HAMLAB_URL/install.sh" | sh -# curl -fsSL "$HAMLAB_URL/install.sh" | sh -s -- --listen 0.0.0.0:8100 --tls auto -# ./install.sh # beside the hamlab-linux-* files: installs offline +# curl -fsSL https://hamlab-demo.bigham.dev/install.sh | sh # -# Everything after `--` goes to `hamlab install` (see `hamlab install -h`). +# With a terminal it walks you through it: who the console runs as, where it listens, +# HTTPS, the firewall, the password — then downloads the build for this machine, checks +# it against SHA256SUMS and hands over to `hamlab install`, which sets up the binary, +# the config and a service that starts at boot. Re-running it upgrades in place. # -# HAMLAB_URL where hamlab-linux- and SHA256SUMS are served from -# HAMLAB_TOKEN access token, when that place is a private Gitea/Forgejo repo +# Without a terminal, with --yes, or with flags after `--`, it asks nothing: +# +# curl -fsSL …/install.sh | sh -s -- --yes +# curl -fsSL …/install.sh | sh -s -- --listen 0.0.0.0:8100 --tls auto --password-stdin , SHA256SUMS and VERSION are served from — +# any static file host, or a Gitea/Forgejo release's download URL +# HAMLAB_TOKEN access token, when that place is a private Gitea/Forgejo repo +# NO_COLOR plain output +# +# POSIX sh: runs under dash, busybox ash and bash alike. set -eu -HAMLAB_URL="${HAMLAB_URL:-https://git.bigham.dev/mitchell/hamlab/releases/download/@VERSION@}" +HAMLAB_URL="${HAMLAB_URL:-https://hamlab-demo.bigham.dev/dl}" +BUILT_FOR="@VERSION@" # filled in by `make release` -die() { echo "install.sh: $*" >&2; exit 1; } - -[ "$(uname -s)" = Linux ] || die "Ham Lab runs on Linux; this is $(uname -s)" -case "$(uname -m)" in - x86_64|amd64) arch=amd64 ;; - aarch64|arm64) arch=arm64 ;; - armv6l|armv7l|armv8l|arm) arch=arm ;; - i386|i486|i586|i686) arch=386 ;; - riscv64) arch=riscv64 ;; - *) die "no build for $(uname -m) (have: amd64, arm64, arm, 386, riscv64)" ;; +# ─────────────────────────────────────────────────────────────── looks ── +if [ -t 2 ] && [ -z "${NO_COLOR:-}" ] && [ "${TERM:-dumb}" != dumb ]; then + B=$(printf '\033[1m'); D=$(printf '\033[2m'); R=$(printf '\033[0m') + AMBER=$(printf '\033[38;5;214m'); GREEN=$(printf '\033[32m'); RED=$(printf '\033[31m'); CYAN=$(printf '\033[36m') +else + B=; D=; R=; AMBER=; GREEN=; RED=; CYAN= +fi +case "${LC_ALL:-${LC_CTYPE:-${LANG:-}}}" in + *UTF-8*|*utf8*|*UTF8*|*utf-8*) OK='✓'; NO='✗'; DOT='•'; ARROW='›'; HR='────────────────────────────────────────────────────────────' ;; + *) OK='ok'; NO='x'; DOT='*'; ARROW='>'; HR='------------------------------------------------------------' ;; esac -file="hamlab-linux-$arch" -tmp=$(mktemp -d 2>/dev/null || mktemp -d -t hamlab) -trap 'rm -rf "$tmp"' EXIT INT TERM +out() { printf '%s\n' "$*" >&2; } +blank() { printf '\n' >&2; } +good() { out " ${GREEN}${OK}${R} $*"; } +note() { out " ${D}$*${R}"; } +warn() { out " ${AMBER}!${R} $*"; } +die() { blank; out " ${RED}${NO}${R} ${B}$*${R}"; blank; exit 1; } +rule() { out "${D}${HR}${R}"; } +step() { blank; out "${AMBER}${B}$1${R} ${B}$2${R}"; [ -z "${3:-}" ] || note "$3"; blank; } +banner() { + blank + out " ${AMBER}${B}Ham Lab${R} ${D}a web console for the machines you run${R}" + out " ${D}terminals ${DOT} monitors ${DOT} services ${DOT} files ${DOT} updates ${DOT} disks — in one tab${R}" + rule +} + +# ─────────────────────────────────────────────────────────── questions ── +# Piped from curl, stdin is this script, so every answer comes from the terminal itself. +TTY=/dev/tty +have_tty() { [ -r "$TTY" ] && [ -w "$TTY" ] && (: <"$TTY") 2>/dev/null; } + +ask() { # ask "Question" "default" → $REPLY + if [ -n "${2:-}" ]; then printf ' %s %s[%s]%s ' "$1" "$D" "$2" "$R" >&2; else printf ' %s ' "$1" >&2; fi + IFS= read -r REPLY <"$TTY" || REPLY= + [ -n "$REPLY" ] || REPLY="${2:-}" +} +confirm() { # confirm "Question" y|n + if [ "$2" = y ]; then hint="Y/n"; else hint="y/N"; fi + while :; do + ask "$1" "$hint" + case "$REPLY" in + "$hint") [ "$2" = y ]; return ;; + y|Y|yes|Yes|YES) return 0 ;; + n|N|no|No|NO) return 1 ;; + *) warn "y or n, please" ;; + esac + done +} +secret() { # secret "Prompt" → $REPLY, typed unseen + printf ' %s ' "$1" >&2 + saved=$(stty -g <"$TTY" 2>/dev/null || true) + stty -echo <"$TTY" 2>/dev/null || true + IFS= read -r REPLY <"$TTY" || REPLY= + [ -z "$saved" ] || stty "$saved" <"$TTY" 2>/dev/null || true + printf '\n' >&2 +} +choose() { # choose "default-number" "label 1" "label 2" … → $REPLY = the number picked + def=$1; shift; n=0 + for label in "$@"; do n=$((n + 1)); if [ "$n" = "$def" ]; then out " ${AMBER}${B}$n${R} $label"; else out " ${B}$n${R} $label"; fi; done + blank + while :; do + ask "Choose 1-$n" "$def" + case "$REPLY" in *[!0-9]*|'') ;; *) if [ "$REPLY" -ge 1 ] && [ "$REPLY" -le "$n" ]; then return; fi ;; esac + warn "a number from 1 to $n, please" + done +} +restore_tty() { [ -z "${saved:-}" ] || stty "$saved" <"$TTY" 2>/dev/null || true; } + +# ──────────────────────────────────────────────────────────── plumbing ── fetch() { # url dest if command -v curl >/dev/null 2>&1; then curl -fsSL ${HAMLAB_TOKEN:+-H "Authorization: token $HAMLAB_TOKEN"} -o "$2" "$1" elif command -v wget >/dev/null 2>&1; then wget -q ${HAMLAB_TOKEN:+--header="Authorization: token $HAMLAB_TOKEN"} -O "$2" "$1" else - die "need curl or wget" + die "This needs curl or wget, and the machine has neither." + fi +} +port_busy() { # is something already listening on TCP port $1? + hex=$(printf '%04X' "$1") + cat /proc/net/tcp /proc/net/tcp6 2>/dev/null | awk -v p=":$hex" '$4 == "0A" && substr($2, length($2) - 4) == p { found = 1 } END { exit !found }' +} +lan_addresses() { # this machine's IPv4 addresses, one per line, most useful first + if command -v ip >/dev/null 2>&1; then + ip -4 -o addr show scope global 2>/dev/null | awk '$2 !~ /^(docker|br-|veth|virbr|cni|flannel|tailscale|wg|tun|tap)/ { split($4, a, "/"); print a[1] " " $2 }' + else + hostname -I 2>/dev/null | tr ' ' '\n' | awk 'NF && $1 !~ /:/ { print $1 " -" }' fi } +# ────────────────────────────────────────────────────────── arguments ── +YES=0; PASS= +while [ $# -gt 0 ]; do + case "$1" in + -y|--yes) YES=1; shift ;; + -h|--help) sed -n '2,20p' "$0" 2>/dev/null | sed 's/^# \{0,1\}//' >&2; exit 0 ;; + --) shift; PASS="$*"; break ;; + *) PASS="$*"; break ;; # flags given straight: they belong to `hamlab install` + esac +done +INTERACTIVE=1 +{ [ "$YES" = 1 ] || [ -n "$PASS" ] || ! have_tty; } && INTERACTIVE=0 + +# ─────────────────────────────────────────────────────────── preflight ── +banner +[ "$(uname -s)" = Linux ] || die "Ham Lab runs on Linux; this is $(uname -s)." +case "$(uname -m)" in + x86_64|amd64) arch=amd64 ;; + aarch64|arm64) arch=arm64 ;; + armv6l|armv7l|armv8l|arm) arch=arm ;; + i386|i486|i586|i686) arch=386 ;; + riscv64) arch=riscv64 ;; + *) die "There is no build for $(uname -m). (There are: amd64, arm64, arm, 386, riscv64.)" ;; +esac +file="hamlab-linux-$arch" + +me=$(id -un 2>/dev/null || echo root) +if [ "$(id -u)" = 0 ]; then how=root +elif command -v sudo >/dev/null 2>&1; then how=sudo +elif command -v doas >/dev/null 2>&1; then how=doas +else die "Installing a service needs root, and there is no sudo or doas here. Run this as root." +fi +if [ -d /run/systemd/system ]; then init=systemd +elif command -v rc-service >/dev/null 2>&1; then init=OpenRC +else init=none +fi +os=$(. /etc/os-release 2>/dev/null && printf '%s' "${PRETTY_NAME:-}"); [ -n "$os" ] || os=Linux +existing=$(command -v hamlab 2>/dev/null || true); [ -n "$existing" ] || { [ -x /usr/local/bin/hamlab ] && existing=/usr/local/bin/hamlab; } || true +have=; [ -z "$existing" ] || have=$("$existing" version 2>/dev/null | awk '{ print $2 }') || true + +good "$os ${D}${DOT}${R} $(uname -m) ${D}${DOT}${R} $init" +case "$how" in root) good "running as root" ;; *) good "running as ${B}$me${R}; $how will ask for root when it is needed" ;; esac +[ "$init" != none ] || warn "no systemd or OpenRC here: Ham Lab will be installed, but nothing will start it at boot" + +# ───────────────────────────────────────────────────── which version ── +tmp=$(mktemp -d 2>/dev/null || mktemp -d -t hamlab) +alt= +cleanup() { restore_tty; rm -rf "$tmp"; [ -z "$alt" ] || rm -f "$alt"; } +trap cleanup EXIT +trap 'blank; out " cancelled — nothing was changed"; exit 130' INT TERM + here=$(cd "$(dirname "$0")" 2>/dev/null && pwd || echo .) -if [ -f "$here/$file" ]; then - cp "$here/$file" "$tmp/hamlab" - [ -f "$here/SHA256SUMS" ] && cp "$here/SHA256SUMS" "$tmp/SHA256SUMS" +offline=0; [ -f "$here/$file" ] && offline=1 +if [ "$offline" = 1 ]; then + ver=$BUILT_FOR; case "$ver" in @*) ver="(local files)" ;; esac else - echo "Downloading $file" >&2 - fetch "$HAMLAB_URL/$file" "$tmp/hamlab" || die "download failed: $HAMLAB_URL/$file" - fetch "$HAMLAB_URL/SHA256SUMS" "$tmp/SHA256SUMS" 2>/dev/null || rm -f "$tmp/SHA256SUMS" + # What is published right now — which may be newer than the copy of this script being run. + ver=$(fetch "$HAMLAB_URL/VERSION" "$tmp/VERSION" 2>/dev/null && head -n 1 "$tmp/VERSION" | tr -cd 'A-Za-z0-9._+-' || true) + [ -n "$ver" ] || { ver=$BUILT_FOR; case "$ver" in @*) ver=latest ;; esac; } +fi +if [ -n "$have" ] && [ "$have" = "$ver" ]; then good "Ham Lab ${B}$have${R} is already installed here ${D}${ARROW}${R} this will reinstall it, keeping its config, user and password" +elif [ -n "$have" ]; then good "Ham Lab ${B}$have${R} is installed here ${D}${ARROW}${R} this will upgrade it to ${B}$ver${R}, keeping its config, user and password" +else good "installing Ham Lab ${B}$ver${R}"; fi + +# ─────────────────────────────────────────────────────────── the wizard ── +flags=; password=; listen=; url_host= +if [ "$INTERACTIVE" = 1 ] && [ -n "$have" ]; then + blank + confirm "$([ "$have" = "$ver" ] && echo Reinstall || echo Upgrade) now?" y || { blank; out " nothing was changed"; exit 0; } +elif [ "$INTERACTIVE" = 1 ]; then + # 1 ─ who + default_user=${SUDO_USER:-${DOAS_USER:-$me}} + step "1/5" "Who should it run as?" "Its terminals are that user's login shell, and its files are that user's files." + while :; do + ask "User" "$default_user"; run_as=$REPLY + id "$run_as" >/dev/null 2>&1 && break + warn "there is no user called \"$run_as\" on this machine" + done + [ "$run_as" != root ] || warn "as root, every terminal it opens is a root shell" + + # 2 ─ where + step "2/5" "Where should it listen?" "Who can reach the console at all. The password still guards it either way." + addrs=$(lan_addresses || true); first=$(printf '%s\n' "$addrs" | awk 'NR == 1 { print $1 }') + if [ -n "$first" ]; then + first_if=$(printf '%s\n' "$addrs" | awk 'NR == 1 { print $2 }') + choose 2 "This machine only ${D}127.0.0.1 — reach it over an SSH tunnel or a reverse proxy${R}" \ + "This network ${D}$first on $first_if — other machines on the LAN can open it${R}" \ + "Every interface ${D}0.0.0.0 — all addresses this machine has or gets${R}" \ + "Another address" + else + choose 1 "This machine only ${D}127.0.0.1${R}" "Every interface ${D}0.0.0.0${R}" "Another address" + [ "$REPLY" = 1 ] || REPLY=$((REPLY + 1)) + fi + case "$REPLY" in + 1) host=127.0.0.1 ;; + 2) host=$first ;; + 3) host=0.0.0.0 ;; + 4) others=$(printf '%s\n' "$addrs" | awk 'NR > 1 { printf "%s%s (%s)", sep, $1, $2; sep = ", " }'); [ -z "$others" ] || note "also on this machine: $others" + while :; do ask "Address" ""; host=$REPLY; case "$host" in ''|*[!0-9a-fA-F.:]*) warn "an IPv4 or IPv6 address, please" ;; *) break ;; esac; done ;; + esac + while :; do + ask "Port" "8100"; port=$REPLY + case "$port" in ''|*[!0-9]*) warn "a number, please"; continue ;; esac + [ "$port" -ge 1 ] && [ "$port" -le 65535 ] || { warn "a port is between 1 and 65535"; continue; } + [ "$port" -ge 1024 ] || [ "$run_as" = root ] || { warn "ports under 1024 need root, and it will run as $run_as"; continue; } + if port_busy "$port"; then warn "something is already listening on port $port"; continue; fi + break + done + case "$host" in *:*) listen="[$host]:$port" ;; *) listen="$host:$port" ;; esac + + # 3 ─ https + step "3/5" "HTTPS?" "A self-signed certificate is made here; browsers warn once, a hub pins its fingerprint." + if [ "$host" = 127.0.0.1 ]; then + choose 2 "HTTPS with a self-signed certificate" "Plain HTTP ${D}— fine here: nothing leaves this machine unencrypted${R}" + else + choose 1 "HTTPS with a self-signed certificate ${D}— recommended: the password crosses the network${R}" "Plain HTTP ${D}— only behind a reverse proxy that adds HTTPS itself${R}" + fi + if [ "$REPLY" = 1 ]; then tls=auto; scheme=https; else tls=off; scheme=http; fi + + # 4 ─ firewall + open_fw=0; fw= + if [ "$host" != 127.0.0.1 ]; then + if command -v firewall-cmd >/dev/null 2>&1 && firewall-cmd --state >/dev/null 2>&1; then fw=firewalld + elif command -v ufw >/dev/null 2>&1 && ufw status 2>/dev/null | grep -q "^Status: active"; then fw=ufw + elif [ "$how" != root ] && command -v ufw >/dev/null 2>&1 && systemctl is-active -q ufw 2>/dev/null; then fw=ufw + fi + fi + step "4/5" "Firewall" + if [ -n "$fw" ]; then + note "$fw is active and will block port $port until it is opened." + blank + if confirm "Open $port/tcp in $fw?" y; then open_fw=1; fi + elif [ "$host" = 127.0.0.1 ]; then good "nothing to open: it only listens on this machine" + else good "no active firewalld or ufw found; nothing to open" + fi + + # 5 ─ password + step "5/5" "The password" "One password opens the console — and the console is a shell on this machine." + choose 1 "Choose one now" "Generate a strong one ${D}— shown once at the end${R}" + if [ "$REPLY" = 1 ]; then + while :; do + secret "Password:"; password=$REPLY + [ ${#password} -ge 12 ] || { warn "at least 12 characters, please (it guards a shell)"; continue; } + secret "Once more:" + [ "$REPLY" = "$password" ] && break + warn "those didn't match; try again" + done + fi + + # summary + if [ "$host" = 0.0.0.0 ] || [ "$host" = "::" ]; then url_host=${first:-this-machine}; else url_host=$host; fi + blank; rule + out " ${B}Ready to install${R}" + blank + out " version ${B}$ver${R} ${D}($file)${R}" + out " runs as ${B}$run_as${R}" + out " address ${B}$scheme://$listen${R}" + if [ "$open_fw" = 1 ]; then out " firewall open $port/tcp in $fw"; fi + if [ -n "$password" ]; then out " password the one you typed"; else out " password generated, shown once"; fi + case "$init" in none) out " service ${AMBER}none — no systemd or OpenRC${R}" ;; *) out " service $init, starts at boot" ;; esac + rule; blank + confirm "Install?" y || { blank; out " nothing was changed"; exit 0; } + + flags="--user $run_as --listen $listen --tls $tls" + [ "$open_fw" = 0 ] || flags="$flags --open-firewall" + [ -z "$password" ] || flags="$flags --password-stdin" fi +# ──────────────────────────────────────────────── download and verify ── +blank +if [ "$offline" = 1 ]; then + cp "$here/$file" "$tmp/hamlab"; [ ! -f "$here/SHA256SUMS" ] || cp "$here/SHA256SUMS" "$tmp/SHA256SUMS" + good "using $file from $here" +else + printf ' %s downloading %s …' "$DOT" "$file" >&2 + fetch "$HAMLAB_URL/$file" "$tmp/hamlab" || { printf '\n' >&2; die "Download failed: $HAMLAB_URL/$file"; } + size=$(wc -c <"$tmp/hamlab" | awk '{ printf "%.1f MB", $1 / 1048576 }') + if [ -t 2 ]; then printf '\r\033[K' >&2; else printf '\n' >&2; fi + good "downloaded $file ${D}($size)${R}" + fetch "$HAMLAB_URL/SHA256SUMS" "$tmp/SHA256SUMS" 2>/dev/null || rm -f "$tmp/SHA256SUMS" +fi if [ -f "$tmp/SHA256SUMS" ] && command -v sha256sum >/dev/null 2>&1; then want=$(awk -v f="$file" '$2 == f || $2 == "*" f { print $1 }' "$tmp/SHA256SUMS") got=$(sha256sum "$tmp/hamlab" | awk '{ print $1 }') - [ -n "$want" ] || die "SHA256SUMS has no entry for $file" - [ "$want" = "$got" ] || die "checksum mismatch for $file" + [ -n "$want" ] || die "SHA256SUMS has no entry for $file." + [ "$want" = "$got" ] || die "Checksum mismatch for $file — the download is not what was published. Nothing was installed." + good "checksum matches SHA256SUMS" +else + warn "no SHA256SUMS (or no sha256sum here): the download was not verified" fi -chmod 0755 "$tmp/hamlab" -bin="$tmp/hamlab" +chmod 0755 "$tmp/hamlab"; bin="$tmp/hamlab" if ! "$bin" version >/dev/null 2>&1; then - # a noexec /tmp is the usual reason: try from the home directory - alt="${HOME:-/root}/.hamlab-install.$$" - trap 'rm -rf "$tmp" "$alt"' EXIT INT TERM + alt="${HOME:-/root}/.hamlab-install.$$" # a noexec /tmp is the usual reason: try from the home directory cp "$bin" "$alt" 2>/dev/null && chmod 0755 "$alt" || true - "$alt" version >/dev/null 2>&1 || die "the $arch build doesn't run on this machine" + "$alt" version >/dev/null 2>&1 || die "The $arch build doesn't run on this machine." bin="$alt" fi -# Piped from curl, stdin is this script: take the password prompt from the terminal. -case " $* " in *password-stdin*) piped=0 ;; *) piped=1 ;; esac -if [ "$piped" = 1 ] && [ ! -t 0 ] && [ -r /dev/tty ] && (: /dev/null; then - "$bin" install "$@" "$log" 2>&1; rc=$? + password= +elif [ "$INTERACTIVE" = 0 ] && [ -n "$PASS" ]; then + log= + case " $PASS " in + *password-stdin*) "$bin" install $PASS; rc=$? ;; + *) if [ ! -t 0 ] && have_tty; then "$bin" install $PASS <"$TTY"; rc=$?; else "$bin" install $PASS; rc=$?; fi ;; + esac else - "$bin" install "$@" + "$bin" install $flags "$log" 2>&1; rc=$? # no typed password and no terminal on stdin: it generates one and prints it fi +set -e +[ -z "$log" ] || sed 's/^/ /' "$log" >&2 +hamlab_bin=$(command -v hamlab 2>/dev/null || echo /usr/local/bin/hamlab) +[ "$rc" = 0 ] && [ -x "$hamlab_bin" ] || die "The install did not finish. Nothing above this line is hidden: the reason is in it." + +# ──────────────────────────────────────────────────────────────── done ── +blank; rule +out " ${GREEN}${B}${OK} Ham Lab $("$hamlab_bin" version 2>/dev/null | awk '{ print $2 }') is installed${R}" +blank +if [ "$init" = none ]; then + out " start it ${B}hamlab serve${R} ${D}as $([ -n "${run_as:-}" ] && echo "$run_as" || echo "its user") — nothing here starts it at boot, so put that under your supervisor${R}" +fi +if [ -n "$listen" ]; then + out " open ${CYAN}${B}$scheme://$(printf '%s' "$listen" | sed "s/^0\.0\.0\.0/$url_host/")${R}" + [ "$tls" != auto ] || out " ${D}your browser will warn about the self-signed certificate once; that is expected${R}" + [ "$host" != 127.0.0.1 ] || out " ${D}from another machine: ssh -L $port:127.0.0.1:$port $run_as@, then http://127.0.0.1:$port${R}" +fi +out " manage ${B}hamlab install --help${R} ${D}change the address, HTTPS, the user — it keeps everything else${R}" +case "$init" in + systemd) out " logs ${B}journalctl -u hamlab -f${R}" ;; + OpenRC) out " logs ${B}tail -f /var/log/hamlab.log${R}" ;; +esac +out " add to hub ${D}on your main Ham Lab: Machines ${ARROW} Add a machine, with this address and password${R}" +out " remove ${B}hamlab uninstall${R} ${D}(--purge deletes its config too)${R}" +rule; blank