Skip to content

Commit 87ae804

Browse files
Sbussisoclaude
andcommitted
fix(install): accept --url/--node-id/--key args, single one-liner for the modal
User report: ran the dashboard's "Install CloudNode" command on a Pi that had a stale ~/data/node.db from earlier `cargo run` testing. install.sh saw the existing node.db, set IS_REGISTERED=true, skipped the setup wizard entirely, and started a systemd service with the revoked credentials. The script said "CloudNode is streaming" — it wasn't, and there was no way to tell from the output. Three changes that converge on a single one-shot install command: 1. install.sh now parses --url, --node-id, and --key from the command line. Use them like: curl -fsSL .../install.sh | bash -s -- \ --url <url> --node-id <id> --key <key> All three required together (fail loud if partial). Unknown args are ignored so future modal versions can append new flags without breaking older script versions. 2. When credentials are passed, the script: a. Treats any existing ~/data/node.db or $INSTALL_DIR/data/node.db as STALE — flips IS_REGISTERED back to false and forces re-registration. Setup overwrites the old DB atomically. b. Runs `sourcebox-sentry-cloudnode setup --url X --node-id Y --key Z` non-interactively. No /dev/tty needed, works in curl|bash pipes. c. Auto-installs the systemd service (no prompt — operator opted in by passing creds). d. On setup failure: exits 1 so the calling shell sees the failure, prints the manual command for retry. Without credentials the script behaves exactly as before: interactive wizard from /dev/tty, optional systemd install with prompt. Existing CI/Ansible/cloud-init flows that don't pass args see no behavior change. 3. AddNodeModal.jsx generates the install command WITH the credentials already inlined, so the operator copies one command instead of two. The previous "1. Install" + "2. Quick Setup" split was the source of the confusion that motivated this fix — install.sh was already trying to do step 2 itself, just badly when stale state existed. Windows still needs two steps (MSI download + post-install setup command in PowerShell) since we don't pipe args into the MSI installer. The modal now only shows the second step on the Windows tab. Verified bash syntax (`bash -n install.sh`) and frontend builds clean. Backwards compatible: every existing install path keeps working; the new path is opt-in via the args. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent c7a2ba3 commit 87ae804

2 files changed

Lines changed: 181 additions & 29 deletions

File tree

backend/scripts/install.sh

Lines changed: 142 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,20 @@
22
set -euo pipefail
33

44
# SourceBox Sentry CloudNode Installer
5-
# Usage: curl -fsSL https://opensentry-command.fly.dev/install.sh | bash
5+
#
6+
# Usage:
7+
# Interactive (downloads binary, then prompts for credentials):
8+
# curl -fsSL https://opensentry-command.fly.dev/install.sh | bash
9+
#
10+
# One-shot (downloads binary, registers non-interactively, starts service):
11+
# curl -fsSL .../install.sh | bash -s -- \
12+
# --url https://opensentry-command.fly.dev \
13+
# --node-id <node_id> \
14+
# --key <api_key>
15+
#
16+
# When --node-id and --key are passed, the script overwrites any stale
17+
# node.db from a previous install (e.g. cargo-run testing) so the new
18+
# credentials actually take effect.
619

720
# ── Colors ──────────────────────────────────────────────────────────
821
RED='\033[0;31m'
@@ -16,6 +29,57 @@ NC='\033[0m'
1629
REPO="SourceBox-LLC/opensentry-cloud-node"
1730
INSTALL_DIR="${SOURCEBOX_SENTRY_INSTALL_DIR:-$HOME/.sourcebox-sentry}"
1831

32+
# ── Parse credential args ──────────────────────────────────────────
33+
# When the dashboard's "Add Node" modal generates the install command
34+
# it now appends --url/--node-id/--key so the operator gets a single
35+
# one-liner that does everything. When invoked without these args
36+
# (e.g. someone hand-typing the curl URL with no creds) the script
37+
# falls back to its old interactive setup-wizard flow.
38+
ARG_URL=""
39+
ARG_NODE_ID=""
40+
ARG_KEY=""
41+
42+
while [ $# -gt 0 ]; do
43+
case "$1" in
44+
--url)
45+
ARG_URL="${2:-}"
46+
shift 2 ;;
47+
--url=*)
48+
ARG_URL="${1#*=}"
49+
shift ;;
50+
--node-id)
51+
ARG_NODE_ID="${2:-}"
52+
shift 2 ;;
53+
--node-id=*)
54+
ARG_NODE_ID="${1#*=}"
55+
shift ;;
56+
--key)
57+
ARG_KEY="${2:-}"
58+
shift 2 ;;
59+
--key=*)
60+
ARG_KEY="${1#*=}"
61+
shift ;;
62+
*)
63+
# Unknown arg — ignore so future install.sh versions can
64+
# accept new flags from older dashboard one-liners without
65+
# crashing the install.
66+
shift ;;
67+
esac
68+
done
69+
70+
# All-or-nothing: if any of the three are set, all three must be.
71+
# Better to fail loud here than to half-configure and leave the
72+
# operator wondering why setup didn't run.
73+
HAVE_QUICK_ARGS=false
74+
if [ -n "$ARG_URL" ] || [ -n "$ARG_NODE_ID" ] || [ -n "$ARG_KEY" ]; then
75+
if [ -z "$ARG_URL" ] || [ -z "$ARG_NODE_ID" ] || [ -z "$ARG_KEY" ]; then
76+
echo -e "${RED}Error: --url, --node-id, and --key must all be provided together.${NC}"
77+
echo -e " Got: --url=${ARG_URL:-<missing>} --node-id=${ARG_NODE_ID:-<missing>} --key=${ARG_KEY:-<missing>}"
78+
exit 1
79+
fi
80+
HAVE_QUICK_ARGS=true
81+
fi
82+
1983
# ── Banner ──────────────────────────────────────────────────────────
2084
echo ""
2185
echo -e "${GREEN}${BOLD} SourceBox Sentry CloudNode Installer${NC}"
@@ -397,29 +461,66 @@ elif [ -f "$INSTALL_DIR/data/node.db" ]; then
397461
DATA_DIR="$INSTALL_DIR"
398462
fi
399463

400-
# ── Run setup wizard interactively ────────────────────────────────
464+
# When credentials were passed on the command line, treat the existing
465+
# node.db as stale (likely from a previous cargo-run / different node)
466+
# and force re-registration with the new creds. Without this, an
467+
# operator clicking "Add Node" → copying the new one-liner → running
468+
# it on a Pi that already has a node.db would silently keep the old
469+
# credentials and start a service that doesn't actually work — exactly
470+
# the bug a user reported tonight.
471+
if [ "$HAVE_QUICK_ARGS" = true ] && [ "$IS_REGISTERED" = true ]; then
472+
echo -e "${YELLOW}Existing node.db detected at ${DATA_DIR}/data/node.db — overwriting with new credentials.${NC}"
473+
# The setup binary itself handles atomic node.db replacement; we
474+
# don't `rm` it here, in case setup fails partway through and the
475+
# operator wants to fall back to the old registration manually.
476+
IS_REGISTERED=false
477+
fi
478+
479+
# ── Run setup (non-interactive when creds passed, wizard otherwise) ─
401480
# A brand-new install is useless without a node_id + api_key, so we
402-
# chain setup straight onto the binary install. The wizard walks the
403-
# operator through "paste the creds from Command Center" and writes
404-
# node.db. We redirect stdin from /dev/tty because `curl | bash`
405-
# leaves stdin as the pipe from curl — without that redirect the
406-
# wizard's first `read` returns EOF and every prompt falls through to
407-
# an empty answer.
481+
# chain setup straight onto the binary install. Two paths now:
482+
#
483+
# Quick path (--url/--node-id/--key passed on the install.sh
484+
# command line): run `setup --url X --node-id Y --key Z` directly.
485+
# No prompts, no /dev/tty needed — works in CI, cloud-init, Ansible,
486+
# anywhere the dashboard's one-liner gets pasted.
487+
#
488+
# Interactive path (no args): run the TUI wizard from /dev/tty so
489+
# the operator can paste creds. We redirect stdin from /dev/tty
490+
# because `curl | bash` leaves stdin as the pipe from curl — without
491+
# that redirect the wizard's first `read` returns EOF and every
492+
# prompt falls through to an empty answer.
408493
SETUP_RAN=false
409-
if [ "$IS_REGISTERED" = false ] && [ -r /dev/tty ] && [ -t 1 ]; then
494+
if [ "$IS_REGISTERED" = false ] && [ "$HAVE_QUICK_ARGS" = true ]; then
495+
# Non-interactive path. Run from $HOME so node.db lands at the
496+
# canonical location the systemd unit uses as WorkingDirectory.
497+
echo ""
498+
echo -e "${BOLD} Registering node with Command Center...${NC}"
499+
if (cd "$HOME" && "$INSTALL_DIR/sourcebox-sentry-cloudnode" setup \
500+
--url "$ARG_URL" --node-id "$ARG_NODE_ID" --key "$ARG_KEY"); then
501+
SETUP_RAN=true
502+
if [ -f "$HOME/data/node.db" ]; then
503+
IS_REGISTERED=true
504+
DATA_DIR="$HOME"
505+
fi
506+
echo -e " ${GREEN}Registered.${NC}"
507+
else
508+
echo -e "${RED}Quick setup failed — check the URL and key, then try again.${NC}"
509+
echo -e " ${CYAN}cd ~ && ${INSTALL_DIR}/sourcebox-sentry-cloudnode setup --url ${ARG_URL} --node-id ${ARG_NODE_ID} --key <key>${NC}"
510+
# Exit non-zero so the calling shell (or the dashboard's one-
511+
# liner copy box) can detect the failure.
512+
exit 1
513+
fi
514+
elif [ "$IS_REGISTERED" = false ] && [ -r /dev/tty ] && [ -t 1 ]; then
515+
# Interactive path — the original wizard flow.
410516
echo ""
411517
echo -e "${BOLD} Register this node with Command Center${NC}"
412518
echo -e " ${DIM}We'll run the setup wizard now. You'll need a node ID and${NC}"
413519
echo -e " ${DIM}API key from ${CYAN}https://opensentry-command.fly.dev${NC}${DIM} → Nodes → Add node.${NC}"
414520
echo ""
415521
if prompt_yes "Run setup wizard now?"; then
416-
# Run from $HOME so node.db lands at the canonical location that
417-
# the systemd unit (below) uses as WorkingDirectory. The wizard
418-
# creates ./data/ relative to CWD.
419522
if (cd "$HOME" && "$INSTALL_DIR/sourcebox-sentry-cloudnode" setup </dev/tty); then
420523
SETUP_RAN=true
421-
# Re-detect registration — if setup succeeded we should now
422-
# find node.db where we expect it.
423524
if [ -f "$HOME/data/node.db" ]; then
424525
IS_REGISTERED=true
425526
DATA_DIR="$HOME"
@@ -531,12 +632,33 @@ UNIT
531632

532633
SERVICE_RUNNING=false
533634
if [ "$PLATFORM" = "linux" ] && check_cmd systemctl && [ -d /etc/systemd/system ]; then
534-
# Skip if we can't prompt (piped, no tty) — never surprise-enable a
535-
# system service in an unattended install.
536-
if [ -t 1 ] && [ -r /dev/tty ]; then
537-
UNIT_EXISTS=false
538-
[ -f /etc/systemd/system/sourcebox-sentry-cloudnode.service ] && UNIT_EXISTS=true
539-
635+
# When we ran setup non-interactively (--url/--node-id/--key),
636+
# also auto-install the systemd service without prompting. The
637+
# operator already opted in by including credentials in the
638+
# one-liner; making them re-confirm via /dev/tty would defeat the
639+
# whole point of "one command does everything."
640+
UNIT_EXISTS=false
641+
[ -f /etc/systemd/system/sourcebox-sentry-cloudnode.service ] && UNIT_EXISTS=true
642+
643+
if [ "$HAVE_QUICK_ARGS" = true ]; then
644+
if [ "$IS_REGISTERED" = true ]; then
645+
echo ""
646+
if [ "$UNIT_EXISTS" = true ]; then
647+
echo -e "${DIM}Refreshing systemd unit + restarting with new credentials...${NC}"
648+
else
649+
echo -e "${DIM}Installing systemd service (auto: --node-id passed)...${NC}"
650+
fi
651+
install_systemd_service || true
652+
else
653+
# Setup didn't actually land — defensive; the non-interactive
654+
# block above exits 1 on setup failure so we shouldn't
655+
# reach here, but if we do, don't start a broken service.
656+
echo -e " ${YELLOW}Skipping systemd — setup didn't complete successfully.${NC}"
657+
fi
658+
elif [ -t 1 ] && [ -r /dev/tty ]; then
659+
# Interactive path — preserve existing behavior. Skip if we
660+
# can't prompt (piped, no tty) — never surprise-enable a
661+
# system service in an unattended install.
540662
if [ "$UNIT_EXISTS" = true ]; then
541663
# Re-run of installer: always overwrite the unit so bug fixes
542664
# (e.g. wrong WorkingDirectory, missing `run` subcommand,

frontend/src/components/AddNodeModal.jsx

Lines changed: 39 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -40,14 +40,33 @@ function AddNodeModal({ isOpen, onClose, onCreate }) {
4040
const base = window.location.origin
4141
// Windows installs via the MSI, not a one-liner — see the Windows
4242
// branch in the install-tab content below.
43+
//
44+
// Linux/macOS: install.sh now accepts --url/--node-id/--key (added
45+
// alongside this commit), so we can hand the operator a SINGLE
46+
// command that downloads the binary, registers the node, and
47+
// starts the systemd service in one shot. Previously this was a
48+
// two-step modal — install.sh + a separate `setup` command — which
49+
// was confusing because install.sh already auto-prompts for setup
50+
// when run interactively, and silently skips it when an old
51+
// node.db is present (caught a real bug tonight where a Pi with
52+
// stale node.db from cargo-run testing started a systemd service
53+
// with revoked credentials).
54+
const linuxInstallCmd = credentials
55+
? `curl -fsSL ${base}/install.sh | bash -s -- --url "${base}" --node-id ${credentials.node_id} --key ${credentials.api_key}`
56+
: `curl -fsSL ${base}/install.sh | bash`
4357
const installCommands = {
44-
linux: `curl -fsSL ${base}/install.sh | bash`,
45-
macos: `curl -fsSL ${base}/install.sh | bash`,
58+
linux: linuxInstallCmd,
59+
macos: linuxInstallCmd,
4660
}
4761
const MSI_DOWNLOAD_URL =
4862
'https://github.com/SourceBox-LLC/opensentry-cloud-node/releases/latest/download/sourcebox-sentry-cloudnode-windows-x86_64.msi'
4963

5064
const exe = os === 'windows' ? 'sourcebox-sentry-cloudnode.exe' : 'sourcebox-sentry-cloudnode'
65+
// Quick setup command — kept for the Windows path (after MSI install,
66+
// operators run this in PowerShell to register the node) and as a
67+
// fallback / re-registration option on Linux/macOS. The Linux
68+
// one-liner above already includes these args, so step 2 is now
69+
// optional / informational on Linux, mandatory on Windows.
5170
const quickSetupCmd = credentials
5271
? `${exe} setup --url "${base}" --node-id ${credentials.node_id} --key ${credentials.api_key}`
5372
: ''
@@ -167,7 +186,7 @@ function AddNodeModal({ isOpen, onClose, onCreate }) {
167186

168187
<div className="deployment-content">
169188
<div className="command-box">
170-
<h5>1. Install CloudNode:</h5>
189+
<h5>{os === 'windows' ? '1. Install CloudNode:' : 'Install + Register (one command):'}</h5>
171190
<div className="install-tabs">
172191
<div className="install-tab-buttons">
173192
<button className={`install-tab-btn${os === 'linux' ? ' active' : ''}`} onClick={() => setOs('linux')}>Linux</button>
@@ -197,13 +216,24 @@ function AddNodeModal({ isOpen, onClose, onCreate }) {
197216
</div>
198217
)}
199218
</div>
200-
<div className="command-box quick-setup-box">
201-
<h5>2. Quick Setup (one command):</h5>
202-
<code className="quick-setup-cmd">{quickSetupCmd}</code>
203-
<button className="btn btn-small" onClick={() => handleCopy(quickSetupCmd)}>Copy</button>
204-
</div>
219+
{/*
220+
Windows still needs the explicit setup step after the
221+
MSI runs. Linux/macOS now have the credentials baked
222+
into the install one-liner above, so this section is
223+
only shown for Windows.
224+
*/}
225+
{os === 'windows' && (
226+
<div className="command-box quick-setup-box">
227+
<h5>2. Quick Setup (one command in PowerShell):</h5>
228+
<code className="quick-setup-cmd">{quickSetupCmd}</code>
229+
<button className="btn btn-small" onClick={() => handleCopy(quickSetupCmd)}>Copy</button>
230+
</div>
231+
)}
205232
<div className="command-note">
206-
This configures the node and starts streaming automatically. Or run <code>{exe} setup</code> for the interactive wizard instead.
233+
{os === 'windows'
234+
? <>This configures the node and starts streaming automatically. Or run <code>{exe} setup</code> for the interactive wizard instead.</>
235+
: <>Downloads the binary, registers the node with these credentials, installs the systemd service, and starts streaming — no extra steps. Or run <code>curl -fsSL {base}/install.sh | bash</code> without arguments to get the interactive setup wizard.</>
236+
}
207237
</div>
208238
</div>
209239
</div>

0 commit comments

Comments
 (0)