Skip to content

Remote installer: curl | sh bootstrap for a fresh Debian VPS - #2

Merged
James-Smart merged 26 commits into
mainfrom
feat/remote-installer
Jul 14, 2026
Merged

James-Smart merged 26 commits into
mainfrom
feat/remote-installer

Conversation

@James-Smart

Copy link
Copy Markdown
Contributor

Summary

Adds deploy/bootstrap.sh — a single, self-contained POSIX-sh installer served at https://outhaul.sh/install, so a fresh Debian VPS goes from nothing to a running Outhaul with:

curl -fsSL https://outhaul.sh/install | sh

Highlights

  • Maximalist terminal UX that degrades gracefully — gradient OUTHAUL hero, braille spinner, colored step ledger, confetti finish; auto-falls back truecolor→256→16→plain (NO_COLOR/non-TTY) and Unicode→ASCII, and emits clean prefixed logs when piped (no escape soup).
  • Interactive wizard reading from /dev/tty (fd 3): ingress mode (Let's Encrypt / Cloudflare Tunnel / local-only), git-push port, optional Nixpacks, optional firewall — with a safe /dev/null fallback (defaults, never stdin) on truly headless pipes.
  • Builds from source with a Go toolchain pinned to go.mod and downloaded from go.dev with SHA256 verification (Debian's apt Go is far too old), plus GOTOOLCHAIN=local so the build can't silently fetch a different toolchain.
  • SSH-safe firewallufw allow every port (incl. SSH/22 and the live SSH session's actual port) before ufw enable, so you can't lock yourself out.
  • DNS + port preflight before ACME (catches the Add Cloudflare Tunnel support #1 Let's Encrypt failure), low-RAM swap guard for the Go build on 1 GB VPSs, and a post-install health check that surfaces the one-time setup URL from journald.
  • Retires the old checkout-only deploy/install.sh (dev path preserved: sudo sh deploy/bootstrap.sh --from-checkout .). Systemd unit unchanged.

Built TDD-first: pure logic (version parse, firewall-port derivation, swap decision, env-file gen, color detection, prompts, setup-URL extraction) is unit-tested with a zero-dependency POSIX harness; a systemd-Debian Docker harness covers the end-to-end path in CI. All error paths were hardened so a failed step reaches a clean die/note rather than aborting mid-install under set -e.

Spec: docs/superpowers/specs/2026-07-13-remote-installer-design.md · Plan: docs/superpowers/plans/2026-07-13-remote-installer.md

Test Plan

  • Unit suite passes under real dash: docker run --rm -v "$PWD:/x" debian:13 dash /x/deploy/test/run.sh → 14 files, 0 failed
  • shellcheck -s sh --severity=warning clean on bootstrap.sh + tests (matches CI)
  • set -e fail-path verified: a failing go build reaches die "build failed" (exit 1), no orphaned spinner
  • Degradation: COLOR=0 emits no ANSI escapes; hero reflows/one-lines under narrow width
  • Firewall port derivation preserves 22 + live SSH port; no duplicates
  • Full end-to-end integration (deploy/test/integration.sh) — runs in CI (needs privileged systemd-in-Docker, not available in the dev sandbox)

🤖 Generated with Claude Code

James-Smart and others added 26 commits July 13, 2026 11:00
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Add init_ui, paint, _c, hr, step_ok, step_fail, note, log_line, die to
deploy/bootstrap.sh. Fix _c to append `|| true` so it never returns
nonzero when COLOR=0 — the bare `[ cond ] && printf ...` form otherwise
trips `set -e` at every call site (step_ok/hr/die/note), aborting the
step before its label ever prints.
Add _hero_rgb, hero, spinner_start/spinner_stop, progress_bar to
deploy/bootstrap.sh. hero degrades to a single line under WIDTH<62 or
plain color. Spinner/progress are cosmetic and short-circuit under
COLOR=0 or non-tty, so they never run in the test suite.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Add a GitHub Actions workflow that shellchecks deploy/bootstrap.sh and its
test suite and runs the unit tests on every deploy/** change. Point the
README's server install instructions at the remote bootstrap.sh one-liner
(and --from-checkout for local dev) instead of the superseded checkout-only
deploy/install.sh, which is now removed.
Address final review: the spinner_start/<work>/spinner_stop idiom aborted
under set -e before spinner_stop and the trailing || die, leaving dead
error handling and (in build_binary) an orphaned spinner. Capture rc under
set +e/set -e at each site so failures reach a clean die/note; move
build_binary's spinner into the parent shell so cleanup can kill it. Also:
fd-3 falls back to /dev/null (safe defaults) not stdin on headless pipes;
journalctl -b avoids stale setup tokens on re-run; preserve the live SSH
session's port so a non-standard sshd port isn't firewalled out.
@James-Smart
James-Smart merged commit 957e807 into main Jul 14, 2026
0 of 2 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant