Merge pull request #1 from claude-hdb/feat/rig

feat: rig CLI — bootstrap, coolify install, curl installer
This commit is contained in:
Daniel Marin 2026-07-11 09:34:50 +01:00 committed by GitHub
commit 9f079bff55
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
7 changed files with 416 additions and 2 deletions

14
.github/workflows/ci.yml vendored Normal file
View file

@ -0,0 +1,14 @@
name: ci
on:
push:
branches: [main]
pull_request:
jobs:
check:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: shellcheck
run: shellcheck install.sh bin/rig commands/*.sh test/cli.sh
- name: cli tests
run: bash test/cli.sh

View file

@ -1,2 +1,73 @@
# deployor
Box-plumbing CLI: curl-install on a pristine Debian server, bootstrap it into a hardened tailnet-joined node
# rig
A CLI that turns a **pristine Debian server into a hardened, tailnet-joined
node** — one curl, one command. A second command installs a version-pinned
Coolify on a control-plane box.
Philosophy (shared with [claudebox](https://github.com/heavy-duty/claudebox)):
**public tool, private state**. rig carries plumbing logic only — no
hostnames, no bindings, no secrets, nothing about *your* infrastructure. It
takes arguments, does its work, and stores no credential, ever.
## Install
```sh
curl -fsSL https://raw.githubusercontent.com/heavy-duty/rig/main/install.sh | bash
```
Installs the tree to `~/.local/share/rig` and links `rig` onto your
PATH (`/usr/local/bin` when root). Re-run any time to upgrade.
## Commands
### `rig bootstrap <control-plane|workload>`
Run as root on the fresh box (over SSH). Convergent — safe to re-run; a
second run changes nothing.
```sh
rig bootstrap control-plane --hostname my-coolify-box
rig bootstrap workload --hostname my-prod-box
```
- `--hostname <name>` — tailnet hostname (default: the role name)
- `--ts-tag <tag>` — tailnet tag to advertise (default: `tag:server`)
What it does: installs `curl ca-certificates unattended-upgrades` (and
enables periodic unattended upgrades); writes an sshd hardening drop-in
(`PermitRootLogin prohibit-password`, `PasswordAuthentication no`); installs
tailscale and joins your tailnet.
**The pre-auth key:** provide it via the `TS_AUTHKEY` env var or type it at
the interactive prompt. Use a **single-use, tagged, short-expiry** key. It
lives in process memory only — rig never writes a credential to disk.
The two roles are identical today except the default hostname; they exist
because control-plane and workload boxes diverge over time, and because the
next command applies to exactly one of them.
### `rig coolify install --version <pin>`
Control-plane box only. Installs Coolify at exactly the pinned version with
`AUTOUPDATE=false` — your deploy tooling is verified against an API surface;
the platform must never move underneath it on its own. Upgrading is an
explicit re-run with a new pin. The pin is required; there is no default.
## What rig deliberately does NOT do
- **Provider firewalls** — Docker publishes ports past host firewalls, so
the real boundary is your cloud provider's firewall, configured outside
this tool.
- **Fetch your config** — boxes never receive repo credentials. Everything
rig needs arrives as arguments or an interactive prompt.
- **Manage deployments** — deploy manifests/executors are separate concerns.
(Planned: the `apply`/`diff` executor half joins rig as commands that
run on operator machines, never on boxes.)
## Testing
`bash test/cli.sh` (dependency-free assertions) + shellcheck run in CI. The
end-to-end rehearsal is a throwaway VM/container: pristine Debian → install →
`bootstrap workload` with a real single-use key → assert the sshd drop-in,
tailnet join, and a no-op second run → destroy, remove the node from the
tailnet.

52
bin/rig Executable file
View file

@ -0,0 +1,52 @@
#!/usr/bin/env bash
set -euo pipefail
ROOT="$(cd "$(dirname "$(readlink -f "${BASH_SOURCE[0]}")")/.." && pwd)"
usage() {
cat <<'EOF'
usage: rig <command> [args]
commands:
bootstrap <control-plane|workload> [--hostname <name>] [--ts-tag <tag>]
OS plumbing on a pristine Debian box: hardening, unattended-upgrades,
tailscale join. Prompts for a single-use tailnet pre-auth key
(TS_AUTHKEY env overrides the prompt). Run as root.
coolify install --version <pin>
Pinned Coolify install (AUTOUPDATE=false). Control-plane box only.
install/upgrade:
curl -fsSL https://raw.githubusercontent.com/heavy-duty/rig/main/install.sh | bash
EOF
}
cmd="${1:-}"
case "$cmd" in
bootstrap)
shift
exec "$ROOT/commands/bootstrap.sh" "$@"
;;
coolify)
shift
sub="${1:-}"
if [ "$sub" != "install" ]; then
usage >&2
exit 2
fi
shift
exec "$ROOT/commands/coolify-install.sh" "$@"
;;
-h|--help|help)
usage
exit 0
;;
"")
usage >&2
exit 2
;;
*)
printf 'rig: unknown command: %s\n' "$cmd" >&2
usage >&2
exit 2
;;
esac

108
commands/bootstrap.sh Executable file
View file

@ -0,0 +1,108 @@
#!/usr/bin/env bash
# rig bootstrap — OS plumbing for a pristine Debian box.
# Convergent: safe to re-run; a second run changes nothing.
set -euo pipefail
log() { printf 'rig-bootstrap: %s\n' "$*"; }
warn() { printf 'rig-bootstrap: WARNING: %s\n' "$*" >&2; }
die() { printf 'rig-bootstrap: ERROR: %s\n' "$1" >&2; exit "${2:-1}"; }
usage() {
cat <<'EOF'
usage: rig bootstrap <control-plane|workload> [--hostname <name>] [--ts-tag <tag>]
--hostname tailnet hostname (default: the role name)
--ts-tag tailnet tag to advertise (default: tag:server)
Provide the single-use tailscale pre-auth key via the TS_AUTHKEY env var, or
enter it at the interactive prompt. It is used once and never written to disk.
EOF
}
# --- args (validated before the root check, so errors are testable) ---------
ROLE="${1:-}"
case "$ROLE" in
control-plane|workload) shift ;;
-h|--help) usage; exit 0 ;;
"") usage >&2; die "role required (control-plane|workload)" 2 ;;
*) die "unknown role: $ROLE (want control-plane|workload)" 2 ;;
esac
TS_HOSTNAME="$ROLE"
TS_TAG="tag:server"
while [ $# -gt 0 ]; do
case "$1" in
--hostname)
[ $# -ge 2 ] || die "--hostname needs a value" 2
TS_HOSTNAME="$2"; shift 2 ;;
--ts-tag)
[ $# -ge 2 ] || die "--ts-tag needs a value" 2
TS_TAG="$2"; shift 2 ;;
*) die "unknown flag: $1" 2 ;;
esac
done
# --- guards ------------------------------------------------------------------
[ "$(id -u)" -eq 0 ] || die "must run as root"
if [ -r /etc/os-release ]; then
# shellcheck source=/dev/null
. /etc/os-release
case "${ID:-} ${ID_LIKE:-}" in
*debian*) ;;
*) warn "not a Debian-family system (ID=${ID:-unknown}); proceeding anyway" ;;
esac
else
warn "cannot read /etc/os-release; proceeding anyway"
fi
# --- pre-auth key (env override, else prompt; never touches disk) ------------
if [ -z "${TS_AUTHKEY:-}" ]; then
read -rsp "tailscale pre-auth key (single-use, tagged, <=1h expiry): " TS_AUTHKEY
echo
fi
[ -n "$TS_AUTHKEY" ] || die "empty pre-auth key"
# --- packages ----------------------------------------------------------------
export DEBIAN_FRONTEND=noninteractive
log "installing base packages"
apt-get update -qq
apt-get install -y -qq curl ca-certificates unattended-upgrades
# enable periodic unattended upgrades (canonical file; idempotent overwrite)
cat > /etc/apt/apt.conf.d/20auto-upgrades <<'EOF'
APT::Periodic::Update-Package-Lists "1";
APT::Periodic::Unattended-Upgrade "1";
EOF
# --- sshd hardening (restart only when the drop-in actually changed) ---------
DROPIN=/etc/ssh/sshd_config.d/99-rig.conf
TMP="$(mktemp)"
cat > "$TMP" <<'EOF'
PermitRootLogin prohibit-password
PasswordAuthentication no
EOF
if ! cmp -s "$TMP" "$DROPIN" 2>/dev/null; then
install -m 0644 "$TMP" "$DROPIN"
systemctl restart ssh
log "sshd hardening drop-in installed"
else
log "sshd hardening drop-in already in place"
fi
rm -f "$TMP"
# --- tailscale ----------------------------------------------------------------
if ! command -v tailscale >/dev/null 2>&1; then
log "installing tailscale"
curl -fsSL https://tailscale.com/install.sh | sh
fi
if tailscale status >/dev/null 2>&1; then
log "tailnet already joined; skipping tailscale up"
else
log "joining tailnet as ${TS_HOSTNAME} (${TS_TAG})"
tailscale up --authkey="$TS_AUTHKEY" --hostname="$TS_HOSTNAME" --advertise-tags="$TS_TAG"
fi
log "done — role ${ROLE}, hostname ${TS_HOSTNAME}"
if [ "$ROLE" = "control-plane" ]; then
log "next: rig coolify install --version <pin>"
fi

42
commands/coolify-install.sh Executable file
View file

@ -0,0 +1,42 @@
#!/usr/bin/env bash
# rig coolify install — pinned Coolify install; AUTOUPDATE=false so the
# platform never self-updates underneath its operators. Upgrades are an
# explicit act.
set -euo pipefail
log() { printf 'rig-coolify: %s\n' "$*"; }
die() { printf 'rig-coolify: ERROR: %s\n' "$1" >&2; exit "${2:-1}"; }
usage() {
cat <<'EOF'
usage: rig coolify install --version <pin>
Installs Coolify at exactly <pin> (e.g. 4.1.2) with AUTOUPDATE=false.
Control-plane box only. The version pin is required — you state the floor
your tooling is verified against; there is no default.
EOF
}
VERSION=""
while [ $# -gt 0 ]; do
case "$1" in
--version)
[ $# -ge 2 ] || die "--version needs a value" 2
VERSION="$2"; shift 2 ;;
-h|--help) usage; exit 0 ;;
*) die "unknown flag: $1" 2 ;;
esac
done
if [ -z "$VERSION" ]; then
usage >&2
die "--version <pin> is required" 2
fi
[ "$(id -u)" -eq 0 ] || die "must run as root"
export AUTOUPDATE=false
log "installing coolify ${VERSION} (AUTOUPDATE=false)"
curl -fsSL https://cdn.coollabs.io/coolify/install.sh -o /tmp/coolify-install.sh
bash /tmp/coolify-install.sh "$VERSION"
log "coolify ${VERSION} installed with AUTOUPDATE=false"
log "next: your bootstrap runbook (admin user, API token, GitHub App, S3 destination)"

70
install.sh Normal file
View file

@ -0,0 +1,70 @@
#!/usr/bin/env bash
set -euo pipefail
# rig installer — intended for: curl -fsSL .../install.sh | bash
#
# Downloads the rig repo tarball, installs the whole tree under $DEST,
# and puts a `rig` symlink on PATH via $BINDIR. Re-run any time to
# upgrade.
REPO="${RIG_REPO:-heavy-duty/rig}"
REF="${RIG_REF:-main}"
DEST="${RIG_HOME:-$HOME/.local/share/rig}"
if [ "$(id -u)" -eq 0 ]; then
BINDIR="${RIG_BIN:-/usr/local/bin}"
else
BINDIR="${RIG_BIN:-$HOME/.local/bin}"
fi
log() { printf 'rig-install: %s\n' "$*"; }
warn() { printf 'rig-install: WARNING: %s\n' "$*" >&2; }
die() { printf 'rig-install: ERROR: %s\n' "$*" >&2; exit 1; }
# --- prerequisites -----------------------------------------------------------
command -v curl >/dev/null 2>&1 || die "curl is required but was not found."
command -v tar >/dev/null 2>&1 || die "tar is required but was not found."
# --- temp workspace ----------------------------------------------------------
TMPDIR="$(mktemp -d)"
cleanup() { rm -rf "$TMPDIR"; }
trap cleanup EXIT
URL="https://github.com/$REPO/archive/refs/heads/$REF.tar.gz"
log "installing rig ($REPO@$REF)"
log "downloading $URL"
curl -fsSL "$URL" -o "$TMPDIR/rig.tar.gz" \
|| die "failed to download $URL"
log "extracting archive"
tar -xzf "$TMPDIR/rig.tar.gz" -C "$TMPDIR" \
|| die "failed to extract archive"
# GitHub archives extract to a single top-level dir like rig-<ref>/
EXTRACTED="$(find "$TMPDIR" -maxdepth 1 -type d -name 'rig-*' | head -n1)"
[ -n "$EXTRACTED" ] || die "could not find extracted rig-* directory in archive"
[ -f "$EXTRACTED/bin/rig" ] || die "archive does not contain bin/rig — is $REPO@$REF correct?"
# --- atomically replace $DEST --------------------------------------------------
log "installing into $DEST"
rm -rf "$DEST"
mkdir -p "$(dirname "$DEST")"
mv "$EXTRACTED" "$DEST"
chmod +x "$DEST/bin/rig" "$DEST"/commands/*.sh
# --- put rig on PATH ------------------------------------------------------
mkdir -p "$BINDIR"
ln -sf "$DEST/bin/rig" "$BINDIR/rig"
log "linked $BINDIR/rig -> $DEST/bin/rig"
# --- PATH check ----------------------------------------------------------------
case ":$PATH:" in
*":$BINDIR:"*) : ;;
*)
warn "$BINDIR is not on your PATH."
warn " add: export PATH=\"$BINDIR:\$PATH\""
;;
esac
log "done — try: rig --help"

57
test/cli.sh Normal file
View file

@ -0,0 +1,57 @@
#!/usr/bin/env bash
# Dependency-free CLI assertions. Run: bash test/cli.sh
# Deliberately no `set -e` — the harness asserts on failing commands.
set -u
ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
PASS=0 FAIL=0
# check <desc> <want_exit> <want_substr> <cmd...>
# Runs cmd, asserts exit code and (if non-empty) that combined output
# contains want_substr.
check() {
local desc="$1" want="$2" substr="$3"; shift 3
local out rc
out="$("$@" 2>&1)"; rc=$?
if [ "$rc" -ne "$want" ]; then
echo "FAIL: $desc — exit $rc, wanted $want"
printf '%s\n' "$out" | sed 's/^/ /'
FAIL=$((FAIL + 1)); return
fi
if [ -n "$substr" ] && ! printf '%s' "$out" | grep -qF -e "$substr"; then
echo "FAIL: $desc — output missing '$substr'"
printf '%s\n' "$out" | sed 's/^/ /'
FAIL=$((FAIL + 1)); return
fi
echo "ok: $desc"; PASS=$((PASS + 1))
}
check "no args shows usage, exit 2" 2 "usage:" "$ROOT/bin/rig"
check "--help exits 0" 0 "usage:" "$ROOT/bin/rig" --help
check "help exits 0" 0 "usage:" "$ROOT/bin/rig" help
check "unknown command exits 2" 2 "unknown command" "$ROOT/bin/rig" frobnicate
check "bare coolify shows usage, exit 2" 2 "usage:" "$ROOT/bin/rig" coolify
check "bootstrap: role required, exit 2" 2 "role required" "$ROOT/commands/bootstrap.sh"
check "bootstrap: --help exits 0" 0 "usage:" "$ROOT/commands/bootstrap.sh" --help
check "bootstrap: unknown role exits 2" 2 "unknown role" "$ROOT/commands/bootstrap.sh" potato
check "bootstrap: unknown flag exits 2" 2 "unknown flag" "$ROOT/commands/bootstrap.sh" workload --nope
check "bootstrap: hostname needs value" 2 "needs a value" "$ROOT/commands/bootstrap.sh" workload --hostname
if [ "$(id -u)" -ne 0 ]; then
check "bootstrap: refuses non-root" 1 "must run as root" env TS_AUTHKEY=x "$ROOT/commands/bootstrap.sh" workload
else
echo "skip: bootstrap non-root refusal (running as root)"
fi
check "coolify: version required, exit 2" 2 "--version" "$ROOT/commands/coolify-install.sh"
check "coolify: --help exits 0" 0 "usage:" "$ROOT/commands/coolify-install.sh" --help
check "coolify: version needs value" 2 "needs a value" "$ROOT/commands/coolify-install.sh" --version
check "coolify: unknown flag exits 2" 2 "unknown flag" "$ROOT/commands/coolify-install.sh" --nope
if [ "$(id -u)" -ne 0 ]; then
check "coolify: refuses non-root" 1 "must run as root" "$ROOT/commands/coolify-install.sh" --version 4.1.2
else
echo "skip: coolify non-root refusal (running as root)"
fi
echo "---"
echo "$PASS passed, $FAIL failed"
[ "$FAIL" -eq 0 ]