install report: first cut, generated by the helpers
Every run writes a timestamped install-report.md recording what was installed, changed, kept, skipped, started and run as root. Written for an adversarial read: the person who just ran a setup script off the internet hands it to an agent of their choosing and asks whether it did anything it should not have. Recorded by the HELPERS rather than by the sections. pkg_install and install_config report themselves, so anything installed or written through them appears whether or not a section author remembered — a section that has to remember is a section that will forget, and an incomplete report is worse than none because it reads as a full account. "Kept" is recorded as carefully as "changed". Leaving somebody's .zshrc alone is the claim a reviewer most wants substantiated, and it is invisible unless stated. Secrets are redacted at the moment of recording rather than filtered at render, so a credential never sits in memory formatted for printing. Verified against a POSTGRES_URL and an api_key/password pair. REPORT_FILE is passed through the sudo re-exec. It was not, first time, and the report silently vanished — the third variable this evening lost to env_reset. Unfinished on purpose, paused mid-task at the owner's request: machine-setup's 26 sections still only report through the two shared helpers, so the sections that change system state directly — systemd units, netplan, ufw, sshd drop-ins — are not yet recorded. That is the half a reviewer would care most about. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -187,6 +187,9 @@ declare -A MACOS_SKIP=(
|
||||
|
||||
step() {
|
||||
CURRENT_STEP="$1"
|
||||
# The report follows the step, rather than each section remembering to say
|
||||
# which one it is. Twenty-six sections, one place.
|
||||
declare -F report_section >/dev/null && report_section "$1"
|
||||
|
||||
if [[ "${OS:-}" == "macos" && -n "${MACOS_SKIP[$1]:-}" ]]; then
|
||||
echo ""
|
||||
|
||||
@@ -43,10 +43,17 @@ install_config() {
|
||||
|
||||
if [[ ! -f "$dest" ]]; then
|
||||
install -D -m 0644 -o "$owner" -g "$(user_group "$owner")" "$src" "$dest"
|
||||
# Recorded here rather than at the call site: "which files did it write" is
|
||||
# the question a reviewer asks first, and a per-section report would drift
|
||||
# from what this function actually did.
|
||||
declare -F report_changed >/dev/null && report_changed "wrote ${dest} (0644, owner ${owner}) — did not exist"
|
||||
return 0
|
||||
fi
|
||||
|
||||
cmp -s "$src" "$dest" && return 1
|
||||
if cmp -s "$src" "$dest"; then
|
||||
declare -F report_kept >/dev/null && report_kept "${dest} already identical to the shipped version — not touched"
|
||||
return 1
|
||||
fi
|
||||
|
||||
echo ""
|
||||
warn "${dest} already exists here, and differs from the one this script ships."
|
||||
@@ -56,6 +63,7 @@ install_config() {
|
||||
# was present to defend.
|
||||
if [[ "${ASSUME_YES:-}" == "1" ]] || [[ ! -t 0 ]]; then
|
||||
echo " keeping yours (nothing was asked, so nothing is replaced)"
|
||||
declare -F report_kept >/dev/null && report_kept "${dest} differs from ours and was KEPT — unattended run, nothing replaced"
|
||||
return 2
|
||||
fi
|
||||
|
||||
@@ -67,17 +75,20 @@ install_config() {
|
||||
if ! read -rp " Which one? (1/2/3) [1]: " answer; then
|
||||
echo ""
|
||||
echo " keeping yours"
|
||||
declare -F report_kept >/dev/null && report_kept "${dest} differs from ours and was KEPT — no answer available"
|
||||
return 2
|
||||
fi
|
||||
case "${answer:-1}" in
|
||||
1)
|
||||
echo " keeping yours"
|
||||
declare -F report_kept >/dev/null && report_kept "${dest} differs from ours and was KEPT by choice"
|
||||
return 2
|
||||
;;
|
||||
2)
|
||||
cp -a "$dest" "${dest}.before-machine-setup"
|
||||
install -D -m 0644 -o "$owner" -g "$(user_group "$owner")" "$src" "$dest"
|
||||
ok "replaced — yours is at ${dest}.before-machine-setup"
|
||||
declare -F report_changed >/dev/null && report_changed "REPLACED ${dest} by choice — previous kept at ${dest}.before-machine-setup"
|
||||
return 0
|
||||
;;
|
||||
3)
|
||||
|
||||
@@ -211,8 +211,20 @@ pkg_install() {
|
||||
LAST_INSTALLED=("${missing[@]}")
|
||||
LAST_KEPT=("${present[@]}")
|
||||
|
||||
announce_plan "$label" present missing || return 0
|
||||
pkg_install_now "${missing[@]}"
|
||||
announce_plan "$label" present missing || {
|
||||
# Declining is a fact a reviewer wants: it explains a package being absent
|
||||
# later without having to guess whether the script failed or was refused.
|
||||
declare -F report_skipped >/dev/null && report_skipped "${label}: declined — ${#missing[@]} package(s) not installed"
|
||||
return 0
|
||||
}
|
||||
|
||||
if pkg_install_now "${missing[@]}"; then
|
||||
declare -F report_installed >/dev/null && ((${#missing[@]})) && report_installed "${PM}: ${missing[*]}"
|
||||
declare -F report_kept >/dev/null && ((${#present[@]})) && report_kept "already present, untouched: ${present[*]}"
|
||||
else
|
||||
declare -F report_failed >/dev/null && report_failed "${PM} install failed: ${missing[*]}"
|
||||
return 1
|
||||
fi
|
||||
}
|
||||
|
||||
# Print what a section is about to do and ask permission for it.
|
||||
|
||||
@@ -48,6 +48,7 @@ done
|
||||
# lib/ so a step can eventually be read — or run — on its own without dragging the
|
||||
# whole script in. Definitions only; nothing in there acts.
|
||||
# shellcheck source=lib/base.sh
|
||||
source "$SCRIPT_DIR/../report.sh"
|
||||
source "$SCRIPT_DIR/lib/base.sh"
|
||||
# shellcheck source=lib/packages.sh
|
||||
source "$SCRIPT_DIR/lib/packages.sh"
|
||||
@@ -87,6 +88,7 @@ echo -e "${BOLD}╚════════════════════
|
||||
[[ -n "${RE_ASK:-}" ]] && rm -f "$ANSWERS_FILE"
|
||||
load_answers
|
||||
|
||||
trap report_flush EXIT
|
||||
detect_os
|
||||
echo ""
|
||||
info "Machine: ${OS_NAME} (${ARCH})"
|
||||
@@ -152,6 +154,7 @@ elif [[ "$EUID" -ne 0 ]]; then
|
||||
OFFICER_ROOT="${OFFICER_ROOT:-}" \
|
||||
SETUP_USERNAME="${SETUP_USERNAME:-}" \
|
||||
MACHINE_ROLE="${MACHINE_ROLE:-}" \
|
||||
REPORT_FILE="${REPORT_FILE:-}" \
|
||||
bash "$SCRIPT_DIR/machine-setup.sh" "$@"
|
||||
fi
|
||||
|
||||
@@ -2521,3 +2524,5 @@ echo ""
|
||||
|
||||
# Clean up progress file on success
|
||||
rm -f "$PROGRESS_FILE"
|
||||
|
||||
report_mark_complete
|
||||
|
||||
@@ -37,6 +37,7 @@ while [[ $# -gt 0 ]]; do
|
||||
done
|
||||
|
||||
# shellcheck source=officer-setup/lib/base.sh
|
||||
source "$SCRIPT_DIR/report.sh"
|
||||
source "$SCRIPT_DIR/officer-setup/lib/base.sh"
|
||||
# shellcheck source=officer-setup/lib/preflight.sh
|
||||
source "$SCRIPT_DIR/officer-setup/lib/preflight.sh"
|
||||
@@ -88,9 +89,12 @@ elif [[ "$EUID" -ne 0 ]]; then
|
||||
OFFICER_ROOT="${OFFICER_ROOT:-}" \
|
||||
SETUP_USERNAME="${SETUP_USERNAME:-}" \
|
||||
MACHINE_ROLE="${MACHINE_ROLE:-}" \
|
||||
REPORT_FILE="${REPORT_FILE:-}" \
|
||||
bash "$SCRIPT_DIR/officer-setup.sh" "$@"
|
||||
fi
|
||||
|
||||
trap report_flush EXIT
|
||||
|
||||
# ── what machine-setup already established ──
|
||||
echo ""
|
||||
if load_machine_answers; then
|
||||
@@ -214,6 +218,7 @@ fi
|
||||
#
|
||||
# Before the repository, because the repository is cloned into it.
|
||||
|
||||
report_section "Layout"
|
||||
step "Layout"
|
||||
if ! skip; then
|
||||
echo ""
|
||||
@@ -257,6 +262,7 @@ fi
|
||||
# 3. Repository
|
||||
# =============================================================================
|
||||
|
||||
report_section "Repository"
|
||||
step "Repository"
|
||||
if ! skip; then
|
||||
PLATFORM_DIR="$(platform_dir)"
|
||||
@@ -329,6 +335,7 @@ fi
|
||||
# 4. Dependencies
|
||||
# =============================================================================
|
||||
|
||||
report_section "Dependencies"
|
||||
step "Dependencies"
|
||||
if ! skip; then
|
||||
echo ""
|
||||
@@ -383,6 +390,7 @@ fi
|
||||
#
|
||||
# POSTGRES_URL is set here and written by the environment section below.
|
||||
|
||||
report_section "Database"
|
||||
step "Database"
|
||||
if ! skip; then
|
||||
echo ""
|
||||
@@ -500,6 +508,7 @@ fi
|
||||
# 6. Environment
|
||||
# =============================================================================
|
||||
|
||||
report_section "Environment"
|
||||
step "Environment"
|
||||
if ! skip; then
|
||||
echo ""
|
||||
@@ -545,6 +554,7 @@ if ! skip; then
|
||||
if confirm "Write it?"; then
|
||||
write_env
|
||||
ok "written, 0600, owned by ${USERNAME}"
|
||||
report_changed "wrote $(env_file) (0600, owner ${USERNAME}) — PORT, PUBLIC_URL, POSTGRES_URL. No secrets: every key lives in the secret store."
|
||||
[[ -f "$(env_file).before-officer-setup" ]] && echo " previous kept as $(env_file).before-officer-setup"
|
||||
SUMMARY+=("Environment: $(env_file)")
|
||||
else
|
||||
@@ -564,6 +574,7 @@ fi
|
||||
# races to create it, and an install that finishes without ever saying the words
|
||||
# "back this up" is one where nobody learns the file matters until it is gone.
|
||||
|
||||
report_section "Secrets"
|
||||
step "Secrets"
|
||||
if ! skip; then
|
||||
echo ""
|
||||
@@ -583,6 +594,7 @@ if ! skip; then
|
||||
if confirm "Create it?"; then
|
||||
if bootstrap_secret_store; then
|
||||
ok "created, 0600, owned by ${USERNAME}"
|
||||
report_changed "created $(secret_store_path) (0600, dir 0700, owner ${USERNAME}) with keys for: jwt, headscale. Generated locally, never transmitted."
|
||||
echo ""
|
||||
warn "back up $(secret_store_path) — and keep it OUT of the backup that holds your database dump."
|
||||
echo " Losing it signs everyone out and makes every encrypted column in"
|
||||
@@ -607,6 +619,7 @@ fi
|
||||
# 8. Schema
|
||||
# =============================================================================
|
||||
|
||||
report_section "Schema"
|
||||
step "Schema"
|
||||
if ! skip; then
|
||||
echo ""
|
||||
@@ -629,6 +642,7 @@ if ! skip; then
|
||||
if confirm "Push it?"; then
|
||||
if OUT="$(push_schema)"; then
|
||||
ok "schema applied"
|
||||
report_changed "applied ${SCHEMA_TABLES} tables to Postgres with 'bun db:push' (drizzle-kit; no migration files)"
|
||||
SUMMARY+=("Schema: ${SCHEMA_TABLES:-?} tables pushed")
|
||||
else
|
||||
warn "db:push failed"
|
||||
@@ -646,6 +660,7 @@ fi
|
||||
# 9. Build
|
||||
# =============================================================================
|
||||
|
||||
report_section "Build"
|
||||
step "Build"
|
||||
if ! skip; then
|
||||
echo ""
|
||||
@@ -666,6 +681,7 @@ if ! skip; then
|
||||
elif confirm "Generate it?"; then
|
||||
if OUT="$(gen_index)"; then
|
||||
ok "$(gen_index_output)"
|
||||
report_changed "generated $(gen_index_output) from index.html, substituting PUBLIC_URL=${ENV_PUBLIC_URL}"
|
||||
SUMMARY+=("Build: index.gen.html for ${ENV_PUBLIC_URL}")
|
||||
else
|
||||
warn "gen:index failed"
|
||||
@@ -683,6 +699,7 @@ fi
|
||||
# 10. Services
|
||||
# =============================================================================
|
||||
|
||||
report_section "Services"
|
||||
step "Services"
|
||||
if ! skip; then
|
||||
echo ""
|
||||
@@ -702,6 +719,7 @@ if ! skip; then
|
||||
if confirm "Write it and start them?"; then
|
||||
write_ecosystem
|
||||
ok "written — $(ecosystem_file)"
|
||||
report_changed "wrote $(ecosystem_file) — six pm2 apps: $(printf '%s ' "${CORE_PROCESSES[@]%%|*}")"
|
||||
|
||||
# Starting against a database that is not answering is not fatal — the server
|
||||
# waits and the agent retries forever — but it makes the Verify section below
|
||||
@@ -713,12 +731,14 @@ if ! skip; then
|
||||
|
||||
if OUT="$(pm2_start)"; then
|
||||
ok "processes started"
|
||||
report_started "pm2 startOrRestart: $(printf '%s ' "${CORE_PROCESSES[@]%%|*}")"
|
||||
pm2_save >/dev/null 2>&1 && ok "process list saved (survives a pm2 restart)"
|
||||
|
||||
echo ""
|
||||
if confirm "Start them on boot too?"; then
|
||||
if pm2_enable_startup; then
|
||||
ok "pm2 will resurrect them at boot"
|
||||
report_ran "pm2 startup systemd — installed a systemd unit so pm2 resurrects these at boot"
|
||||
SUMMARY+=("Services: 6 processes started, enabled at boot")
|
||||
else
|
||||
warn "could not enable the boot hook — run 'pm2 startup' yourself and follow it"
|
||||
@@ -743,6 +763,7 @@ fi
|
||||
# 11. Verify
|
||||
# =============================================================================
|
||||
|
||||
report_section "Verify"
|
||||
step "Verify"
|
||||
if ! skip; then
|
||||
echo ""
|
||||
@@ -791,3 +812,5 @@ fi
|
||||
echo ""
|
||||
echo -e "${BOLD} Pre-flight complete.${NC} The remaining sections are not built yet."
|
||||
echo ""
|
||||
|
||||
report_mark_complete
|
||||
|
||||
@@ -0,0 +1,184 @@
|
||||
#!/bin/bash
|
||||
# =============================================================================
|
||||
# The install report
|
||||
# =============================================================================
|
||||
#
|
||||
# Every run writes a timestamped markdown file recording what it installed, what
|
||||
# it changed, what it left alone, and what it ran as root.
|
||||
#
|
||||
# ── Who it is for ──
|
||||
#
|
||||
# Not us. It exists so the person who just ran a setup script off the internet
|
||||
# can hand the result to an agent of THEIR choosing and ask "did this do anything
|
||||
# it should not have". That is an adversarial read by someone who does not trust
|
||||
# us, which decides almost every choice below:
|
||||
#
|
||||
# Facts, not narration. "installed docker-ce" is checkable. "set up Docker" is
|
||||
# a claim. Every entry names the thing precisely enough to verify against the
|
||||
# machine afterwards.
|
||||
#
|
||||
# Recorded by the HELPERS, not by the sections. A section that has to remember
|
||||
# to report is a section that will forget, and an incomplete report is worse
|
||||
# than none — it reads as a full account. `pkg_install` and `install_config`
|
||||
# record themselves, so anything installed or written through them appears
|
||||
# whether or not the section author thought about it.
|
||||
#
|
||||
# Kept and skipped are recorded too. "Left your .zshrc alone" is the claim a
|
||||
# reviewer most wants substantiated, and it is invisible unless stated.
|
||||
#
|
||||
# NO SECRETS. The whole point is that this file gets shared. Passwords, keys
|
||||
# and connection strings are redacted at the moment of recording rather than
|
||||
# filtered later — see `report_redact`.
|
||||
#
|
||||
# ── Shape ──
|
||||
#
|
||||
# Facts accumulate in an array during the run and the file is rendered at the
|
||||
# end, so a crash halfway leaves no half-written report claiming to be complete.
|
||||
# `report_flush` is called by the exit trap, which marks it INCOMPLETE and says
|
||||
# where it stopped.
|
||||
|
||||
[[ -n "${OFFICER_REPORT_LOADED:-}" ]] && return 0
|
||||
OFFICER_REPORT_LOADED=1
|
||||
|
||||
REPORT_FACTS=()
|
||||
REPORT_SECTION="(start)"
|
||||
REPORT_STARTED="$(date '+%Y-%m-%d %H:%M:%S %Z')"
|
||||
REPORT_COMPLETE=false
|
||||
|
||||
# Where it goes. install.sh exports REPORT_FILE so both halves land in ONE file;
|
||||
# a half run on its own makes its own.
|
||||
report_path() {
|
||||
if [[ -n "${REPORT_FILE:-}" ]]; then
|
||||
echo "$REPORT_FILE"
|
||||
return
|
||||
fi
|
||||
local base="${OFFICER_ROOT:-${USER_HOME:-$HOME}}"
|
||||
[[ -d "$base" ]] || base="${USER_HOME:-$HOME}"
|
||||
echo "${base}/install-report-$(date '+%Y%m%d-%H%M%S').md"
|
||||
}
|
||||
|
||||
# Redact anything that looks like a credential.
|
||||
#
|
||||
# Applied when the fact is RECORDED, not when it is rendered, so a secret never
|
||||
# sits in memory formatted for printing and cannot be leaked by a future change
|
||||
# to the renderer. Deliberately blunt: a password that survives is a leak, a URL
|
||||
# over-redacted is an inconvenience.
|
||||
report_redact() {
|
||||
sed -E \
|
||||
-e 's#(://[^:/@[:space:]]+):[^@[:space:]]+@#\1:REDACTED@#g' \
|
||||
-e 's#((password|passwd|secret|token|key|apikey|api_key)[[:space:]]*[=:][[:space:]]*)[^[:space:]]+#\1REDACTED#gI'
|
||||
}
|
||||
|
||||
report_section() { REPORT_SECTION="$1"; }
|
||||
|
||||
# One fact. `kind` is what a reviewer scans for: installed, kept, changed,
|
||||
# skipped, ran, started, failed.
|
||||
report_fact() {
|
||||
local kind="$1" text="$2"
|
||||
REPORT_FACTS+=("${REPORT_SECTION}|${kind}|$(printf '%s' "$text" | report_redact | tr '\n' ' ')")
|
||||
}
|
||||
|
||||
report_installed() { report_fact installed "$1"; }
|
||||
report_kept() { report_fact kept "$1"; }
|
||||
report_changed() { report_fact changed "$1"; }
|
||||
report_skipped() { report_fact skipped "$1"; }
|
||||
report_started() { report_fact started "$1"; }
|
||||
report_failed() { report_fact failed "$1"; }
|
||||
|
||||
# A command run with privilege. The reviewer's first question is "what did it run
|
||||
# as root", and the honest answer is a list rather than a promise.
|
||||
report_ran() { report_fact ran "$1"; }
|
||||
|
||||
report_mark_complete() { REPORT_COMPLETE=true; }
|
||||
|
||||
# Render. Safe to call twice; the trap and a normal finish both reach it.
|
||||
report_flush() {
|
||||
local dest kinds k
|
||||
dest="$(report_path)"
|
||||
[[ -n "${REPORT_WRITTEN:-}" ]] && return 0
|
||||
REPORT_WRITTEN=1
|
||||
|
||||
{
|
||||
echo "# Officer install report"
|
||||
echo ""
|
||||
if $REPORT_COMPLETE; then
|
||||
echo "**Status:** finished."
|
||||
else
|
||||
echo "**Status: INCOMPLETE — the run stopped during \`${REPORT_SECTION}\`.**"
|
||||
echo "Everything below still happened; what comes after it did not."
|
||||
fi
|
||||
echo ""
|
||||
echo "| | |"
|
||||
echo "| --- | --- |"
|
||||
echo "| started | ${REPORT_STARTED} |"
|
||||
echo "| finished | $(date '+%Y-%m-%d %H:%M:%S %Z') |"
|
||||
echo "| host | $(hostname 2>/dev/null || echo unknown) |"
|
||||
echo "| system | $(uname -srm) |"
|
||||
echo "| account | ${USERNAME:-$(id -un)} |"
|
||||
echo "| script commit | $(git -C "${SCRIPT_DIR:-.}" rev-parse --short HEAD 2>/dev/null || echo 'not a git checkout') |"
|
||||
echo ""
|
||||
echo "---"
|
||||
echo ""
|
||||
echo "## How to review this"
|
||||
echo ""
|
||||
echo "This file exists so you can hand it to someone — or something — that does"
|
||||
echo "not trust the script that wrote it. It is a list of facts, each meant to be"
|
||||
echo "checkable against the machine rather than taken on faith."
|
||||
echo ""
|
||||
echo "Worth asking of it:"
|
||||
echo ""
|
||||
echo "- Does anything under **installed** come from somewhere other than your"
|
||||
echo " distribution's repositories, Homebrew, or a vendor's documented installer?"
|
||||
echo "- Does anything under **changed** touch a file outside this install, your"
|
||||
echo " home directory, or the system configuration a setup script would be"
|
||||
echo " expected to touch?"
|
||||
echo "- Does anything under **ran** do more than the section it sits under claims?"
|
||||
echo "- Is anything **started** that you did not ask for?"
|
||||
echo ""
|
||||
echo "Credentials are redacted where they were recorded. If you find one that is"
|
||||
echo "not, that is a bug worth reporting — this file is meant to be shareable."
|
||||
echo ""
|
||||
echo "What this report does NOT cover: anything a package's own post-install"
|
||||
echo "script did. Reviewing \`docker-ce\` itself is a different exercise from"
|
||||
echo "reviewing the script that installed it."
|
||||
echo ""
|
||||
echo "---"
|
||||
echo ""
|
||||
|
||||
if ((${#REPORT_FACTS[@]} == 0)); then
|
||||
echo "_Nothing was recorded — no section made a change._"
|
||||
else
|
||||
local last=""
|
||||
local line section kind text
|
||||
for line in "${REPORT_FACTS[@]}"; do
|
||||
section="${line%%|*}"
|
||||
kind="${line#*|}"; kind="${kind%%|*}"
|
||||
text="${line#*|*|}"
|
||||
if [[ "$section" != "$last" ]]; then
|
||||
[[ -n "$last" ]] && echo ""
|
||||
echo "## ${section}"
|
||||
echo ""
|
||||
last="$section"
|
||||
fi
|
||||
printf -- '- **%s** — %s\n' "$kind" "$text"
|
||||
done
|
||||
fi
|
||||
|
||||
echo ""
|
||||
echo "---"
|
||||
echo ""
|
||||
echo "## Summary by kind"
|
||||
echo ""
|
||||
for k in installed changed kept skipped started ran failed; do
|
||||
local n
|
||||
n="$(printf '%s\n' "${REPORT_FACTS[@]}" | grep -c "|${k}|" || true)"
|
||||
printf -- '- %-10s %s\n' "$k" "$n"
|
||||
done
|
||||
} >"$dest" 2>/dev/null
|
||||
|
||||
[[ -n "${USERNAME:-}" ]] && chown "${USERNAME}:$(id -gn "$USERNAME" 2>/dev/null || echo "$USERNAME")" "$dest" 2>/dev/null || true
|
||||
chmod 0644 "$dest" 2>/dev/null || true
|
||||
|
||||
echo ""
|
||||
echo " Install report: ${dest}"
|
||||
}
|
||||
Reference in New Issue
Block a user