feat: thin install.sh onboarding bootstrap (EGB-671)
Ships in the repo (clone already done). Checks age + jq + git, then PRINTS the PATH line, onboarding next-steps, upgrade one-liner, and key-transfer hint. Never edits shell rc, never runs sudo (prints the command). Exits non-zero with an install hint when a dependency is missing.
This commit is contained in:
parent
6319313ee4
commit
7b041af68b
2 changed files with 185 additions and 0 deletions
113
install.sh
Executable file
113
install.sh
Executable file
|
|
@ -0,0 +1,113 @@
|
|||
#!/usr/bin/env bash
|
||||
#
|
||||
# secrets — thin onboarding bootstrap (EGB-671).
|
||||
#
|
||||
# This script ships INSIDE the repo: you already cloned the repo to get it, so
|
||||
# its only jobs are (1) verify the dependencies the tool needs and (2) print the
|
||||
# exact commands to finish setup. It deliberately does NOT:
|
||||
# - edit your shell rc files (it prints the PATH line for you to paste)
|
||||
# - invoke sudo or install packages behind your back (it prints the command)
|
||||
# - re-implement any of the tool's security logic
|
||||
#
|
||||
# This is a security tool whose whole pitch is "verify, don't trust" — so the
|
||||
# installer holds itself to a higher bar than convenience, not a lower one.
|
||||
#
|
||||
# Usage:
|
||||
# ./install.sh # check deps, print setup + next steps
|
||||
# ./install.sh --help
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
# Resolve the directory this script lives in (the cloned tool repo). Uses bash
|
||||
# builtins only so it works under a minimal PATH.
|
||||
_src="${BASH_SOURCE[0]}"
|
||||
TOOL_DIR="$(cd "${_src%/*}" 2>/dev/null && pwd)"
|
||||
|
||||
usage() {
|
||||
cat <<EOF
|
||||
install.sh — finish setting up the 'secrets' tool.
|
||||
|
||||
Run this once after cloning the repo. It verifies dependencies (age, jq, git)
|
||||
and prints the commands to put 'secrets' on your PATH and onboard a machine.
|
||||
|
||||
Usage:
|
||||
./install.sh Check dependencies and print setup + next steps
|
||||
./install.sh --help Show this help
|
||||
|
||||
It never edits your shell config and never runs sudo — it prints the exact
|
||||
commands so you stay in control (this is a secrets tool, after all).
|
||||
|
||||
Onboarding after setup:
|
||||
First machine: secrets init --remote <your-private-repo-url>
|
||||
Other machine: secrets join --remote <your-private-repo-url> --key <key.txt>
|
||||
EOF
|
||||
}
|
||||
|
||||
# Print the install command for a package, using whatever package manager is
|
||||
# present. For sudo-requiring managers we PRINT the line for you to run — the
|
||||
# installer never escalates on its own.
|
||||
install_hint() {
|
||||
local pkg="$1"
|
||||
if command -v brew >/dev/null 2>&1; then
|
||||
echo "brew install $pkg"
|
||||
elif command -v apt-get >/dev/null 2>&1; then
|
||||
echo "sudo apt-get install -y $pkg"
|
||||
elif command -v dnf >/dev/null 2>&1; then
|
||||
echo "sudo dnf install -y $pkg"
|
||||
else
|
||||
echo "install '$pkg' with your system package manager"
|
||||
fi
|
||||
}
|
||||
|
||||
case "${1:-}" in
|
||||
--help|-h) usage; exit 0 ;;
|
||||
"") ;;
|
||||
*) echo "Unknown option: $1" >&2; usage >&2; exit 2 ;;
|
||||
esac
|
||||
|
||||
echo "secrets — bootstrap check (tool dir: $TOOL_DIR)"
|
||||
echo ""
|
||||
|
||||
# Dependency check. age + jq + git are all load-bearing on the cold-start path:
|
||||
# jq became required once .secrets.json (manifest) is JSON, so it must be present
|
||||
# BEFORE the first manifest read.
|
||||
missing=0
|
||||
for dep in git age jq; do
|
||||
if command -v "$dep" >/dev/null 2>&1; then
|
||||
echo " ok $dep"
|
||||
else
|
||||
echo " MISSING $dep — install it with:"
|
||||
echo " $(install_hint "$dep")"
|
||||
missing=1
|
||||
fi
|
||||
done
|
||||
echo ""
|
||||
|
||||
if [ "$missing" -ne 0 ]; then
|
||||
echo "Install the missing dependencies above, then re-run ./install.sh." >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
cat <<EOF
|
||||
All dependencies present. Two steps to finish:
|
||||
|
||||
1) Put 'secrets' on your PATH. Add this line to your shell config
|
||||
(~/.zshrc or ~/.bashrc), then restart your terminal:
|
||||
|
||||
export PATH="$TOOL_DIR:\$PATH"
|
||||
|
||||
2) Onboard this machine:
|
||||
|
||||
First machine (new vault):
|
||||
secrets init --remote <your-private-repo-url>
|
||||
# then transfer key.txt to your other machines (AirDrop / scp / USB):
|
||||
# scp <this-host>:$HOME/.secrets/key.txt ~/.secrets/key.txt
|
||||
|
||||
Other machine (join an existing vault):
|
||||
secrets join --remote <your-private-repo-url> --key <path-to-key.txt>
|
||||
# 'join' clones the vault, installs the key, and VERIFIES it decrypts
|
||||
# before declaring success — a mis-copied key fails loudly, not silently.
|
||||
|
||||
To update the tool later:
|
||||
git -C "$TOOL_DIR" pull
|
||||
EOF
|
||||
72
test/install.bats
Normal file
72
test/install.bats
Normal file
|
|
@ -0,0 +1,72 @@
|
|||
#!/usr/bin/env bats
|
||||
# EGB-671: install.sh thin bootstrap. It ships IN the repo (you clone the repo
|
||||
# to get it), so its job is: verify deps (age + jq + git), PRINT the PATH line
|
||||
# and next-step commands — never edit dotfiles, never invoke sudo. Security-rail
|
||||
# concerns are operator-local (.ship-policy.json); these are functional checks.
|
||||
|
||||
load test_helper
|
||||
|
||||
INSTALL_SH="$(cd "$(dirname "${BATS_TEST_FILENAME}")/.." && pwd)/install.sh"
|
||||
|
||||
@test "install.sh exists and is executable" {
|
||||
[ -f "$INSTALL_SH" ]
|
||||
[ -x "$INSTALL_SH" ]
|
||||
}
|
||||
|
||||
@test "install.sh --help prints usage and exits 0" {
|
||||
run "$INSTALL_SH" --help
|
||||
[ "$status" -eq 0 ]
|
||||
[[ "$output" == *"install.sh"* ]] || false
|
||||
[[ "$output" == *"join"* ]] || false
|
||||
}
|
||||
|
||||
@test "install.sh prints the PATH export line for the tool dir (does not edit rc)" {
|
||||
local tool_dir
|
||||
tool_dir="$(cd "$(dirname "$INSTALL_SH")" && pwd)"
|
||||
run "$INSTALL_SH"
|
||||
[ "$status" -eq 0 ]
|
||||
[[ "$output" == *"export PATH="* ]] || false
|
||||
[[ "$output" == *"$tool_dir"* ]] || false
|
||||
# It must NOT have written to any shell rc in the isolated HOME.
|
||||
[ ! -f "$HOME/.zshrc" ]
|
||||
[ ! -f "$HOME/.bashrc" ]
|
||||
}
|
||||
|
||||
@test "install.sh prints both onboarding next-steps (init --remote and join)" {
|
||||
run "$INSTALL_SH"
|
||||
[ "$status" -eq 0 ]
|
||||
[[ "$output" == *"secrets init --remote"* ]] || false
|
||||
[[ "$output" == *"secrets join --remote"* ]] || false
|
||||
}
|
||||
|
||||
@test "install.sh prints the upgrade one-liner" {
|
||||
run "$INSTALL_SH"
|
||||
[ "$status" -eq 0 ]
|
||||
[[ "$output" == *"git -C"* ]] || false
|
||||
[[ "$output" == *"pull"* ]] || false
|
||||
}
|
||||
|
||||
@test "install.sh prints a key-transfer hint" {
|
||||
run "$INSTALL_SH"
|
||||
[ "$status" -eq 0 ]
|
||||
[[ "$output" == *"key.txt"* ]] || false
|
||||
}
|
||||
|
||||
@test "install.sh never invokes sudo (prints it for the user instead)" {
|
||||
# No executed 'sudo' — any sudo reference must be quoted guidance text.
|
||||
run grep -nE '^[[:space:]]*sudo ' "$INSTALL_SH"
|
||||
[ "$status" -ne 0 ]
|
||||
}
|
||||
|
||||
@test "install.sh reports a missing dependency with an install hint and non-zero exit" {
|
||||
# Build a minimal PATH that has the tools install.sh needs but NOT jq.
|
||||
local fake="$TEST_TMPDIR/fakebin"
|
||||
mkdir -p "$fake"
|
||||
for t in bash uname env cat grep sed tr dirname command age git printf; do
|
||||
src="$(command -v "$t" 2>/dev/null || true)"
|
||||
[ -n "$src" ] && ln -sf "$src" "$fake/$t" 2>/dev/null || true
|
||||
done
|
||||
run env PATH="$fake" "$INSTALL_SH"
|
||||
[ "$status" -ne 0 ]
|
||||
[[ "$output" == *"jq"* ]] || false
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue