ceos-alpine-installer/lib/ce-common.sh
John A. Hoeven e1ee30812f Fix first real-hardware install issues from tinkerpad run
- bootstrap: install bash before fetch (ash-only base installs)
- bootstrap/deploy: correct URL (drop /repo prefix, webroot is /srv/repo)
- 01_core: remove bash (bootstrap now handles it)
- ce-base/minimal/basic: BusyBox-compatible df check (drop --output=avail)
- ce-common: add ensure_testing_repo + install_pkg_testing
- 12_access: espeakup + espeakup-openrc from edge/testing
- 05_fonts: nerd-fonts → nerd-fonts-all (meta-package blocks by design)
- 06_utils: tealdeer + bash-completion from edge/testing
- 07_journal: jrnl test checks ~/.local/bin/jrnl (pipx not in PATH during install)
- 11_wiki: arch-aware CE_REPO (${ARCH} replaces hardcoded amd64); Forgejo TODO updated

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-11 21:55:21 +02:00

198 lines
6.7 KiB
Bash

#!/usr/bin/env bash
# Created by John A. Hoeven with the ethical assistance of Claude AI
# ---------------------------------------------------------------------------
# ce-common.sh
# ~/projects/ceos/alpine-installer/lib/ce-common.sh
# Version: v0.0.1 | Status: DEVELOPMENT
# Target: Alpine Linux x86-64 (tinkerpad / ThinkPad T480)
# ---------------------------------------------------------------------------
# Shared library: logging, run_test, package helpers, common guards.
# Source this file; do not execute directly.
# ---------------------------------------------------------------------------
# Guard against double-sourcing
[[ -n "${CE_COMMON_LOADED:-}" ]] && return 0
CE_COMMON_LOADED=1
# Architecture detection
ARCH="$(uname -m)"
# Logging setup — SCRIPT_NAME must be set before sourcing
LOG_DIR="${HOME}/.local/logs/ceos_installer"
SCRIPT_NAME="${SCRIPT_NAME:-ce-common}"
LOG_FILE="${LOG_DIR}/${SCRIPT_NAME}-$(date +%Y-%m-%d).log"
# Failed packages tracker — shared across all sourced subscripts
FAILED_PKGS=()
# Packages requiring manual follow-up (distinct from simple failures)
MANUAL_PKGS=()
mkdir -p "${LOG_DIR}"
# ---------------------------------------------------------------------------
# Logging
# ---------------------------------------------------------------------------
_log() {
local level="${1}" msg="${2}" ts
ts="$(date '+%Y-%m-%d %H:%M:%S')"
printf '[%s] [%s] %s\n' "${ts}" "${level}" "${msg}" | tee -a "${LOG_FILE}"
}
log_info() { _log "INFO " "${1}"; }
log_warn() { _log "WARN " "${1}" >&2; }
log_error() { _log "ERROR" "${1}" >&2; }
log_debug() { [[ "${CE_DEBUG:-0}" == "1" ]] && _log "DEBUG" "${1}"; }
# ---------------------------------------------------------------------------
# run_test <description> <test_expression> <fail|warn>
# ---------------------------------------------------------------------------
run_test() {
local desc="${1}" cmd="${2}" mode="${3:-fail}"
if eval "${cmd}" >/dev/null 2>&1; then
log_info " PASS: ${desc}"
return 0
fi
if [[ "${mode}" == "fail" ]]; then
log_error " FAIL: ${desc}"
exit 1
else
log_warn " WARN: ${desc}"
return 1
fi
}
# ---------------------------------------------------------------------------
# Package helpers
# ---------------------------------------------------------------------------
# install_pkg <package> — one package, logs result
install_pkg() {
local pkg="${1}"
if doas apk add "${pkg}" >> "${LOG_FILE}" 2>&1; then
log_info "Installed: ${pkg}"
return 0
else
log_warn "FAILED: ${pkg} — logged, continuing"
FAILED_PKGS+=("${pkg}")
return 1
fi
}
# install_pkg_edge <package> — install with @edge tag, warn on fail
install_pkg_edge() {
local pkg="${1}"
if doas apk add "${pkg}@edge" >> "${LOG_FILE}" 2>&1; then
log_info "Installed: ${pkg} (@edge)"
return 0
else
log_warn "FAILED: ${pkg}@edge — manual verification required"
FAILED_PKGS+=("${pkg} (edge, manual verification required)")
return 1
fi
}
# ensure_edge_repo — add @edge community tag if not present
ensure_edge_repo() {
if ! grep -q "^@edge" /etc/apk/repositories 2>/dev/null; then
log_info "Adding @edge community tag to /etc/apk/repositories"
printf '@edge https://dl-cdn.alpinelinux.org/alpine/edge/community\n' \
| doas tee -a /etc/apk/repositories > /dev/null 2>&1 \
|| log_warn "Could not add @edge tag — edge installs may fail"
fi
}
# ensure_testing_repo — add @testing tag if not present
ensure_testing_repo() {
if ! grep -q "^@testing" /etc/apk/repositories 2>/dev/null; then
log_info "Adding @testing edge/testing tag to /etc/apk/repositories"
printf '@testing https://dl-cdn.alpinelinux.org/alpine/edge/testing\n' \
| doas tee -a /etc/apk/repositories > /dev/null 2>&1 \
|| log_warn "Could not add @testing tag — testing installs may fail"
fi
}
# install_pkg_testing <package> — install with @testing tag, warn on fail
install_pkg_testing() {
local pkg="${1}"
if doas apk add "${pkg}@testing" >> "${LOG_FILE}" 2>&1; then
log_info "Installed: ${pkg} (@testing)"
else
log_warn "FAILED: ${pkg}@testing — manual verification required"
FAILED_PKGS+=("${pkg} (testing, manual verification required)")
fi
}
# ---------------------------------------------------------------------------
# Guards and prompts
# ---------------------------------------------------------------------------
# check_not_root — exit if accidentally run as root
check_not_root() {
if [[ "${EUID}" -eq 0 ]]; then
echo "ERROR: Do not run as root. Run as a standard user with doas configured." >&2
exit 1
fi
}
# confirm_proceed — single prompt; bypassed when CE_INSTALL_NO_CONFIRM=1
confirm_proceed() {
[[ "${CE_INSTALL_NO_CONFIRM:-0}" == "1" ]] && return 0
echo ""
read -r -p "Proceed with installation? [y/N] " confirm
[[ "${confirm,,}" == "y" ]] || { log_info "Aborted by user."; exit 0; }
}
# ---------------------------------------------------------------------------
# Summary
# ---------------------------------------------------------------------------
print_summary() {
log_info "=== INSTALLATION SUMMARY ==="
if [[ ${#FAILED_PKGS[@]} -eq 0 && ${#MANUAL_PKGS[@]} -eq 0 ]]; then
log_info "All packages installed successfully."
return 0
fi
if [[ ${#FAILED_PKGS[@]} -gt 0 ]]; then
log_warn "Failed or skipped packages:"
for pkg in "${FAILED_PKGS[@]}"; do
log_warn " - ${pkg}"
done
fi
if [[ ${#MANUAL_PKGS[@]} -gt 0 ]]; then
log_warn "Requires manual follow-up:"
for item in "${MANUAL_PKGS[@]}"; do
log_warn " - ${item}"
done
fi
}
# review_pause <label> [log_file]
# Greps the tier log for WARN/ERROR lines and displays them.
# If issues found: blocks until OHIOD confirms reviewed.
# If clean: logs OK and continues without prompting.
# Not bypassed by CE_INSTALL_NO_CONFIRM — this is an OHIOD safety gate.
review_pause() {
local label="${1:-this step}"
local log="${2:-${LOG_FILE}}"
local issues
issues="$(grep -E '\[(WARN |ERROR)\]' "${log}" 2>/dev/null || true)"
echo ""
if [[ -n "${issues}" ]]; then
log_warn "=== REVIEW REQUIRED — ${label} ==="
echo ""
echo "${issues}"
echo ""
read -r -p "Address any issues above. Ready to proceed? [y/N] " confirm
[[ "${confirm,,}" == "y" ]] \
|| { log_info "Stopped at ${label} — address issues and re-run from this tier."; exit 0; }
else
log_info "${label} — no issues found, continuing."
fi
}