Add bash styleguide

This commit is contained in:
John A. Hoeven 2026-06-13 06:12:12 +02:00
commit 287924c066

852
bash-styleguide Normal file
View file

@ -0,0 +1,852 @@
# CE OS Script Style Guide — Bash
<!-- /ceos/CE_OS_ScriptStyleGuide_v0_0_2_EN.md -->
<!-- Version: v0.0.2 | Status: DEVELOPMENT -->
<!-- Created: 2026-05-27 | Updated: 2026-06-13 | Language: EN -->
<!-- Created by John A. Hoeven with the ethical assistance of Claude AI -->
---
## Purpose
This document defines the mandatory style and structural standards for all
Bash scripts produced under the CE OS / Cervello Elettrico umbrella. Every
script — installer, utility, tier component, library, or one-off tool — must
conform to these rules before it is considered fit for review, testing, or
promotion.
A Python style guide will be produced separately once sufficient CE OS Python
use cases exist to ground it in real experience rather than theory.
These rules encode hard-won lessons from real hardware deployment across Alpine
aarch64, Alpine armhf, Alpine x86, Debian amd64, and NixOS. They exist because
silent failures, missed cleanups, and untested assumptions have caused real
problems on real machines. Follow them without exception.
CE OS scripts are also teaching documents. An MUO-level reader — someone
competent but not a professional developer — should be able to follow any
script and understand what it does and why. Clarity is not optional.
---
## Part 1 — Universal Rules
These rules apply to every Bash script regardless of its role (orchestrator,
subscript, or library).
### 1.1 Attribution header
Every script — without exception — carries this attribution in its header:
```
Created by John A. Hoeven with the ethical assistance of Claude AI
```
This is non-negotiable. It appears in every file, every time.
### 1.2 Version and status
Every script header includes version and status:
```
Version: v0.0.1 | Status: DEVELOPMENT
```
Version tracks follow CE OS conventions:
| Stage | Format | Increment rule |
|-------------|----------|---------------------------------------|
| Development | `v0.0.x` | Third point on every meaningful edit |
| Testing | `v0.x.x` | Second point on milestones |
| Stable | `vX.Y` | Major/minor on scope/compatibility |
### 1.3 Update and upgrade first
Every script that installs or modifies packages must run the OS package
manager's update and upgrade sequence as its very first system-touching
action — before any package installation, before any capability test.
The exact command depends on the detected OS (see §2.6 and §3.6). No
exceptions. A script that installs packages without first updating the
package index is defective.
### 1.4 Architecture awareness
Every script that installs packages must:
1. Detect the running architecture (`uname -m`)
2. Test that each required package is available for that architecture
before attempting installation
3. Log clearly which packages are unavailable and why
4. Never silently skip a package — always surface the gap to the OHIOD
### 1.5 Test before, during, and after
Testing is not optional and not an afterthought. Every script has three
explicit test gates:
- **Pre-flight** — before any system changes. Checks environment,
dependencies, architecture, disk space, connectivity. All warn-only.
No changes made at this stage.
- **Inline** — after each significant operation. Verifies the operation
succeeded before proceeding. Hard-fail on critical operations; warn-only
on optional components.
- **Post-install** — after all operations complete. Verifies installed
tools, written config files, service state. Hard-fail if required
components are absent.
Test results are written to the log file with timestamps. A script with
no test gates is incomplete.
### 1.6 Cleanup before exit
Every script registers a cleanup handler that runs on all exits — normal,
error, and signal. The cleanup handler must:
- Remove all temporary files and directories created by the script
- Clear the package manager's download cache
- Remove any lock files created by the script
- Log that cleanup completed
A script that leaves debris is defective.
### 1.7 No silent failures
Every error is logged and surfaced. A script that swallows errors and
reports success is worse than one that fails loudly. If an operation
cannot be completed, say so clearly, log it, and either hard-fail or
warn — never silently skip.
### 1.8 Credentials
Never write real credentials in any script, config file, or comment.
Use `[PLACEHOLDER]` format always. This applies to passwords, tokens,
API keys, private keys, and any other secret material.
### 1.9 Never run as root
CE OS scripts run as the OHIOD user and invoke `doas` for privileged
operations. Scripts must not require or assume direct root execution.
If a script is accidentally run as root, it should warn and exit.
### 1.10 Script length limits
CE OS Bash scripts follow these length limits. Lines are counted as written,
including comments — comments are part of the script, not overhead.
| Script role | Soft limit | Hard limit | Notes |
|---|---|---|---|
| Subscript | 150 lines | 200 lines | Split or extract at hard limit |
| Library | 150 lines | 200 lines | Split by function group if needed |
| Orchestrator | None | None | Coordinators by nature; no logic to limit |
**Soft limit (150):** Review the script. Can a function be extracted to a
library? Can a phase be split into a subscript? If yes, do it. If the script
is genuinely cohesive at this length, proceed — but document why in the header.
**Hard limit (200):** Split or extract. This is the default action. No
argument is needed — just do it.
**Hard limit override:** In rare cases, keeping a script intact beyond 200
lines is the correct decision. This is permitted, but must be declared
explicitly in the script header:
```bash
# Line limit override: <concise reason why splitting would harm clarity or correctness>
```
A silent override is not an override — it is a defect.
Orchestrators are excluded from all limits. An orchestrator that sources
five libraries and calls twelve subscripts in sequence may be 300 lines of
clean, readable flow control. That is correct. Logic belongs in libraries
and subscripts; the orchestrator reads like a table of contents.
### 1.11 Scripts as teaching documents
CE OS scripts are read by people learning the system, not only by people
maintaining it. Write accordingly.
- **Comments explain intent, not mechanics.** `# Install vim` is noise.
`# vim is the CE OS default editor — required for all tiers` is information.
- **Complex functions belong in libraries.** A 50-line function embedded
in a subscript forces the reader to context-switch mid-flow. Extracted
to a library with a clear header comment, it can be read and understood
in isolation.
- **If a function needs a comment block longer than itself, extract it.**
The comment is telling you the function is too complex to live inline.
- **The orchestrator is the lesson plan.** A reader following an orchestrator
should understand the full install flow without opening a single sourced
file. Each call to a subscript or library function should be self-evident
from its name and any inline comment.
- **Target register: MUO level.** Make It Understandable and Obvious.
A competent but non-professional reader should be able to follow the logic.
If they cannot, the script needs work — not the reader.
---
## Part 2 — Bash Style Guide
### 2.1 Shebang and script class
CE OS uses two Bash script classes. Choose the correct one:
**Class A — System scripts**
OpenRC service scripts, `/etc/local.d/` scripts, bootstrap phases,
anything that runs before CE OS layers are confirmed present.
```sh
#!/bin/sh
# POSIX sh / ash only. No bash features.
```
**Class B — CE OS scripts**
All installer scripts, operational scripts, tier components, user-facing
tools. Everything written for CE OS day-to-day use.
```bash
#!/usr/bin/env bash
# Bash features permitted. BusyBox tools on PATH.
```
When in doubt, write Class B. Use Class A only when you have a specific
reason to require ash compatibility.
**The bootstrap exception:** The opening lines of an installer that runs
before bash is confirmed must be ash-safe, then hand off:
```sh
#!/bin/sh
# Bootstrap phase — ash only until bash is confirmed
command -v bash >/dev/null 2>&1 || {
echo "Installing bash..."
doas apk add bash
}
exec bash "$0" "$@"
# Everything below this line is normal CE OS bash
```
### 2.2 Full header block
Every CE OS Bash script carries this header in full:
```bash
#!/usr/bin/env bash
# Created by John A. Hoeven with the ethical assistance of Claude AI
# ---------------------------------------------------------------------------
# <script-name>.sh
# /opt/ceos/scripts/<script-name>.sh
# Version: v0.0.1 | Status: DEVELOPMENT
# ---------------------------------------------------------------------------
# Purpose: One-line description of what this script does.
# Target: Which nodes / contexts this runs on (e.g. Alpine aarch64 Pi 5)
# Entry: How it is invoked (e.g. doas bash phase1.sh, or ./tool.sh --flag)
# Depends: ce-common.sh, ce-test-lib.sh (list sourced libraries)
# ---------------------------------------------------------------------------
# Phases:
# 0 — Pre-flight (environment checks, no system changes)
# 1 — <description>
# 2 — <description>
# N — Cleanup (always runs via trap)
# ---------------------------------------------------------------------------
```
All fields are mandatory. Leave none blank. If a field does not apply,
write `N/A` — do not delete the line.
### 2.3 Strict mode — selectively applied
Do **not** use `set -e` in CE OS scripts. It causes unpredictable exits
on operations that are allowed to fail (package availability checks,
optional installs, test assertions). CE OS scripts handle errors explicitly.
Use these instead:
```bash
set -u # Treat unset variables as errors
set -o pipefail # Propagate pipe failures
```
Apply `set -u` and `set -o pipefail` after sourcing libraries. Handle
each potential failure explicitly with conditional checks.
### 2.4 Variables and naming
```bash
# Constants — UPPER_SNAKE_CASE
readonly SCRIPT_VERSION="v0.0.1"
readonly SCRIPT_NAME="$(basename "$0")"
readonly LOG_DIR="${HOME}/.local/logs/ceos_installer"
readonly ARCH="$(uname -m)"
# Local variables — lower_snake_case
local pkg_name=""
local install_result=0
# Loop variables — short, descriptive
for pkg in "${packages[@]}"; do ...
# Arrays — UPPER for constants, lower for mutable
readonly REQUIRED_PKGS=( bash vim tmux )
available_pkgs=()
```
Always quote variable expansions: `"${VAR}"` not `$VAR`. Always.
### 2.5 Functions
```bash
# Function declaration style — no function keyword
check_architecture() {
local expected_arch="${1:?check_architecture: expected_arch required}"
local actual_arch
actual_arch="$(uname -m)"
if [[ "${actual_arch}" != "${expected_arch}" ]]; then
log_warn "Architecture mismatch: expected ${expected_arch}, got ${actual_arch}"
return 1
fi
log_info "Architecture confirmed: ${actual_arch}"
return 0
}
```
Rules:
- No `function` keyword — use `name() {` style
- Every function documents its arguments with `${VAR:?message}` or explicit checks
- All functions `return 0` on success, non-zero on failure
- Local variables declared with `local` — never pollute global scope
- Functions are grouped: library sourcing, then utilities, then phases, then main
### 2.6 Package manager abstraction
Detect the OS and set package manager variables at script start:
```bash
detect_package_manager() {
if command -v apk >/dev/null 2>&1; then
PKG_MANAGER="apk"
PKG_UPDATE="apk update"
PKG_UPGRADE="apk upgrade"
PKG_INSTALL="apk add"
PKG_CACHE_CLEAN="rm -rf /var/cache/apk/*"
PKG_QUERY="apk info -e"
elif command -v apt-get >/dev/null 2>&1; then
PKG_MANAGER="apt"
PKG_UPDATE="apt-get update"
PKG_UPGRADE="apt-get upgrade -y"
PKG_INSTALL="apt-get install -y"
PKG_CACHE_CLEAN="apt-get clean && rm -rf /var/lib/apt/lists/*"
PKG_QUERY="dpkg -l"
elif command -v nix-env >/dev/null 2>&1; then
PKG_MANAGER="nix"
PKG_UPDATE="nix-channel --update"
PKG_UPGRADE="nixos-rebuild switch"
PKG_INSTALL="nix-env -iA"
PKG_CACHE_CLEAN="nix-collect-garbage -d"
PKG_QUERY="nix-env -q"
else
log_error "No supported package manager found"
exit 1
fi
log_info "Package manager: ${PKG_MANAGER}"
}
# Run update and upgrade BEFORE any package installs
run_package_update() {
log_info "Running package update..."
doas ${PKG_UPDATE} || { log_error "Package update failed"; exit 1; }
log_info "Running package upgrade..."
doas ${PKG_UPGRADE} || { log_error "Package upgrade failed"; exit 1; }
log_info "Package update and upgrade complete"
}
```
### 2.7 Architecture-aware package testing
Before installing any package, test availability for the running architecture.
For Alpine, the search order is:
1. Alpine stable (main)
2. Alpine community
3. Alpine edge
4. CE APK repo on Forgejo (`CE_APK_REPO` — packages that must be built for CE OS)
Set the CE repo URL as a constant at the top of any script that may need it:
```bash
# CE APK repository — packages built by Cervello Elettrico
# Replace [PLACEHOLDER] with the live Forgejo APK repo URL when available
readonly CE_APK_REPO="[PLACEHOLDER]"
```
```bash
test_package_available() {
local pkg="${1:?test_package_available: pkg required}"
local arch="${ARCH}"
case "${PKG_MANAGER}" in
apk)
# Check official Alpine repos (stable, community, edge) first
if apk search -x "${pkg}" 2>/dev/null | grep -q "^${pkg}-"; then
log_info " FOUND (Alpine repo): ${pkg}"
return 0
fi
# Fall back to CE APK repo on Forgejo
if [[ -n "${CE_APK_REPO:-}" ]] && \
curl --silent --max-time 5 \
"${CE_APK_REPO}/${arch}/APKINDEX.tar.gz" \
| tar -xzO 2>/dev/null \
| grep -q "^P:${pkg}$"; then
log_info " FOUND (CE repo): ${pkg}"
return 0
fi
;;
apt)
apt-cache show "${pkg}" >/dev/null 2>&1 && return 0
;;
nix)
nix-env -qaP "${pkg}" >/dev/null 2>&1 && return 0
;;
esac
log_warn "Package '${pkg}' not available for ${arch} on ${PKG_MANAGER}"
return 1
}
install_packages() {
local -n pkg_list="${1:?install_packages: pkg_list required}"
local failed=()
local unavailable=()
for pkg in "${pkg_list[@]}"; do
if ! test_package_available "${pkg}"; then
unavailable+=( "${pkg}" )
continue
fi
if ! doas ${PKG_INSTALL} "${pkg}"; then
log_error "Failed to install: ${pkg}"
failed+=( "${pkg}" )
else
log_info "Installed: ${pkg}"
fi
done
if [[ ${#unavailable[@]} -gt 0 ]]; then
log_warn "Unavailable for ${ARCH}: ${unavailable[*]}"
log_warn "These packages were skipped. Review CE repo for alternatives."
fi
if [[ ${#failed[@]} -gt 0 ]]; then
log_error "Installation failures: ${failed[*]}"
return 1
fi
return 0
}
```
### 2.8 Logging
Source `ce-common.sh` for the standard logging functions. If writing a
standalone script that cannot source `ce-common.sh`, implement these
four levels minimally:
```bash
# Minimal logging — use ce-common.sh log functions when available
_log() {
local level="${1}"
local msg="${2}"
local 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}"; }
```
Log file location: `~/.local/logs/ceos_installer/` (XDG compliant).
Log file name: `<script-name>-<YYYY-MM-DD>.log`.
### 2.9 Cleanup trap
Register the cleanup trap immediately after defining the cleanup function,
before any work begins:
```bash
# ── Cleanup ────────────────────────────────────────────────────────────────
TEMP_DIR=""
cleanup() {
local exit_code="${?}"
log_info "Running cleanup..."
# Remove temp files
if [[ -n "${TEMP_DIR}" && -d "${TEMP_DIR}" ]]; then
rm -rf "${TEMP_DIR}"
log_info "Removed temp dir: ${TEMP_DIR}"
fi
# Clear package manager cache
case "${PKG_MANAGER:-}" in
apk) doas rm -rf /var/cache/apk/* 2>/dev/null || true ;;
apt) doas apt-get clean 2>/dev/null || true
doas rm -rf /var/lib/apt/lists/* 2>/dev/null || true ;;
nix) : ;; # nix cache managed separately
esac
log_info "Cleanup complete (exit code: ${exit_code})"
}
trap cleanup EXIT INT TERM HUP
# Create temp dir only after trap is registered
TEMP_DIR="$(mktemp -d)"
```
### 2.10 Test gates — structure
```bash
# ── Pre-flight ─────────────────────────────────────────────────────────────
phase_preflight() {
log_info "=== PRE-FLIGHT ==="
local pass=0
# Each check: run_test <description> <command> [warn|fail]
run_test "bash version >= 4" '[[ "${BASH_VERSINFO[0]}" -ge 4 ]]' fail
run_test "running as non-root" '[[ "${EUID}" -ne 0 ]]' fail
run_test "internet reachable" \
'curl --silent --max-time 5 --output /dev/null https://cervelloelettrico.it' \
warn
run_test "architecture detected" '[[ -n "${ARCH}" ]]' fail
run_test "disk space >= 500MB" \
'[[ $(df / --output=avail | tail -1) -ge 512000 ]]' \
warn
log_info "Pre-flight complete"
}
# ── Inline test (example) ──────────────────────────────────────────────────
phase_install_vim() {
log_info "=== INSTALLING VIM ==="
doas ${PKG_INSTALL} vim || { log_error "vim install failed"; return 1; }
# Inline test — verify immediately after install
run_test "vim installed" 'command -v vim >/dev/null 2>&1' fail
log_info "vim install verified"
}
# ── Post-install ───────────────────────────────────────────────────────────
phase_verify() {
log_info "=== POST-INSTALL VERIFICATION ==="
local required_tools=( vim tmux starship bash )
for tool in "${required_tools[@]}"; do
run_test "${tool} available" "command -v ${tool} >/dev/null 2>&1" fail
done
log_info "All required tools verified"
}
```
The `run_test` function lives in `ce-test-lib.sh`. When writing a
standalone script, implement a minimal version:
```bash
run_test() {
local desc="${1}"
local cmd="${2}"
local mode="${3:-fail}" # fail | warn
if eval "${cmd}" >/dev/null 2>&1; then
log_info " PASS: ${desc}"
return 0
else
if [[ "${mode}" == "fail" ]]; then
log_error " FAIL: ${desc}"
exit 1
else
log_warn " WARN: ${desc}"
return 1
fi
fi
}
```
### 2.11 User confirmation
One confirmation prompt before system modifications begin. No more:
```bash
confirm_proceed() {
local prompt="${1:-Proceed with installation?}"
printf '\n%s [y/N] ' "${prompt}"
read -r response
case "${response}" in
[yY]|[yY][eE][sS]) return 0 ;;
*) log_info "Aborted by Organic Humanoid I/O Device unit ID ${USER}"; exit 0 ;;
esac
}
```
### 2.12 POSIX / BusyBox compatibility
For Class B scripts running on Alpine, prefer BusyBox-safe patterns:
| Avoid (GNU-only) | Use instead (BusyBox-safe) |
|------------------------|----------------------------------------------------|
| `grep -P` | `grep -E` |
| `sed 's/\w//'` | `sed 's/[[:alnum:]_]//'` |
| `find -printf` | `find ... -exec basename {} \;` or pipe to `awk` |
| `stat -c "%Y" f` | `stat -t f \| awk '{print $12}'` |
| `dd status=progress` | plain `dd`, or `pv` if progress needed |
| `ping` in scripts | `curl --silent --max-time 5 --output /dev/null URL`|
### 2.13 Script architecture — orchestrators, subscripts, and libraries
CE OS Bash scripts follow a three-layer architecture. Understanding which
layer a script belongs to determines its structure, its line limits, and
what it is permitted to contain.
#### The three layers
**Libraries (`ce-*-lib.sh`)**
A library is a collection of related functions and constants that other
scripts source. Libraries contain no executable code at the top level —
they are sourced, not run. Running a library directly should produce no
output and no side effects.
Extract a function to a library when:
- More than one script needs it (reuse)
- The function is complex enough that embedding it disrupts the flow of
the script it lives in (clarity)
- The function requires a comment block longer than itself to be understood
(the comment is telling you it belongs in isolation)
Naming: `ce-<domain>-lib.sh` — e.g. `ce-pkg-lib.sh`, `ce-net-lib.sh`,
`ce-test-lib.sh`.
```bash
#!/usr/bin/env bash
# Created by John A. Hoeven with the ethical assistance of Claude AI
# ---------------------------------------------------------------------------
# ce-pkg-lib.sh
# /opt/ceos/lib/ce-pkg-lib.sh
# Version: v0.0.1 | Status: DEVELOPMENT
# ---------------------------------------------------------------------------
# Purpose: Package manager detection, update, and availability testing.
# Used by: ce-install.sh, ce-base.sh, ce-minimal.sh
# ---------------------------------------------------------------------------
# This file is a library. Source it — do not run it directly.
# ---------------------------------------------------------------------------
# Guard against double-sourcing
[[ -n "${CE_PKG_LIB_LOADED:-}" ]] && return 0
readonly CE_PKG_LIB_LOADED=1
detect_package_manager() { ... }
test_package_available() { ... }
run_package_update() { ... }
```
**Subscripts (`ce-*.sh`)**
A subscript owns a single phase or a coherent group of related operations.
It accepts arguments, performs its work, and returns a meaningful exit code.
Subscripts may be run directly for testing and debugging — this is a feature,
not a side effect.
Naming: `ce-<phase>.sh` or `ce-<domain>.sh` — e.g. `ce-base.sh`,
`ce-minimal.sh`, `ce-audio.sh`.
Subject to the 150-line soft / 200-line hard limit. If a subscript is
growing beyond this, extract complex functions to a library first, then
consider whether the phase itself should be split.
**Orchestrators (`ce-install.sh` or similar)**
An orchestrator coordinates subscripts and libraries. It contains:
- Sourcing of required libraries
- Calling subscripts in sequence
- Inter-phase safety gates (explicit `[y/N]` confirmation before each tier)
- The master cleanup trap
- Flow control based on subscript exit codes
An orchestrator contains **no complex logic of its own.** If something
complex needs to happen, it lives in a subscript or library. The orchestrator
calls it. A reader following the orchestrator should understand the complete
flow without opening any sourced file.
Orchestrators have no line limit. A 300-line orchestrator that sources six
libraries and calls ten subscripts in sequence is correct and expected.
#### Sourcing libraries
```bash
# Source libraries — paths relative to script location
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
readonly LIB_DIR="${SCRIPT_DIR}/../lib"
# shellcheck source=../lib/ce-common-lib.sh
source "${LIB_DIR}/ce-common-lib.sh" || {
echo "ERROR: ce-common-lib.sh not found at ${LIB_DIR}" >&2
exit 1
}
# shellcheck source=../lib/ce-pkg-lib.sh
source "${LIB_DIR}/ce-pkg-lib.sh" || {
echo "ERROR: ce-pkg-lib.sh not found at ${LIB_DIR}" >&2
exit 1
}
```
Always validate that sourced files exist and loaded correctly. A missing
library is a hard failure — never silently proceed without it.
#### Directory layout
```
/opt/ceos/
├── lib/
│ ├── ce-common-lib.sh # Logging, run_test, confirm_proceed
│ ├── ce-pkg-lib.sh # Package manager detection and operations
│ ├── ce-test-lib.sh # Test gate framework
│ └── ce-net-lib.sh # Network checks
├── scripts/
│ ├── ce-install.sh # Orchestrator
│ ├── ce-base.sh # Tier 0 subscript
│ ├── ce-minimal.sh # Tier 1 subscript
│ └── ce-basic.sh # Tier 2 subscript
```
#### Summary table
| Layer | Naming | Contains | Line limit |
|--------------|--------------------|-----------------------------------|---------------------|
| Library | `ce-*-lib.sh` | Functions and constants only | 150 soft / 200 hard |
| Subscript | `ce-*.sh` | Single phase or domain logic | 150 soft / 200 hard |
| Orchestrator | `ce-install.sh` | Flow, gates, sourcing, trap only | None |
---
## Part 3 — Python Style Guide
*The Python style guide will be produced as a separate document once
sufficient CE OS Python use cases exist to ground it in real experience.
Do not apply Bash conventions to Python scripts — wait for the dedicated
guide.*
---
## Part 4 — Quick Reference
### 4.1 Mandatory checklist — every Bash script
Before any script is considered ready for review, verify all of the following:
```
[ ] Attribution header present and exact
[ ] Version and Status in header
[ ] Script role declared (orchestrator / subscript / library)
[ ] Line limit override comment present if >200 lines (subscripts and libraries)
[ ] Package manager detected, not assumed
[ ] doas ${PKG_UPDATE} && doas ${PKG_UPGRADE} runs first (if installing packages)
[ ] Architecture detected (uname -m) before any package operations
[ ] Each package tested: Alpine stable → community → edge → CE repo
[ ] CE_APK_REPO constant present if CE repo check is used
[ ] Pre-flight test gate implemented (warn-only, no changes)
[ ] Inline tests after each significant operation
[ ] Post-install verification gate implemented
[ ] Cleanup trap registered before any work begins
[ ] Cleanup removes: temp files, /var/cache/apk/*, lock files
[ ] Cleanup runs on EXIT INT TERM HUP
[ ] No silent failures — every error logged and surfaced
[ ] No set -e — errors handled explicitly
[ ] No credentials in code — [PLACEHOLDER] only
[ ] No direct root execution — doas for privileged ops
[ ] Single confirmation prompt before system changes begin
[ ] Log file written to ~/.local/logs/ceos_installer/
[ ] Comments explain intent, not mechanics
[ ] Complex functions extracted to library if they disrupt script flow
```
### 4.2 Header templates — copy/paste
**Orchestrator:**
```bash
#!/usr/bin/env bash
# Created by John A. Hoeven with the ethical assistance of Claude AI
# ---------------------------------------------------------------------------
# ce-install.sh
# /opt/ceos/scripts/ce-install.sh
# Version: v0.0.1 | Status: DEVELOPMENT
# Role: Orchestrator
# ---------------------------------------------------------------------------
# Purpose:
# Target:
# Entry:
# Sources: ce-common-lib.sh, ce-pkg-lib.sh, ce-test-lib.sh
# Calls: ce-base.sh, ce-minimal.sh, ce-basic.sh
# ---------------------------------------------------------------------------
# Phases:
# 0 — Pre-flight (environment checks, no system changes)
# 1 — Base (Tier 0)
# 2 — Minimal (Tier 1)
# 3 — Basic (Tier 2)
# N — Cleanup (always runs via trap)
# ---------------------------------------------------------------------------
```
**Subscript:**
```bash
#!/usr/bin/env bash
# Created by John A. Hoeven with the ethical assistance of Claude AI
# ---------------------------------------------------------------------------
# ce-base.sh
# /opt/ceos/scripts/ce-base.sh
# Version: v0.0.1 | Status: DEVELOPMENT
# Role: Subscript — Tier 0 Base installation
# ---------------------------------------------------------------------------
# Purpose:
# Target:
# Entry:
# Depends: ce-common-lib.sh, ce-pkg-lib.sh
# ---------------------------------------------------------------------------
```
**Library:**
```bash
#!/usr/bin/env bash
# Created by John A. Hoeven with the ethical assistance of Claude AI
# ---------------------------------------------------------------------------
# ce-pkg-lib.sh
# /opt/ceos/lib/ce-pkg-lib.sh
# Version: v0.0.1 | Status: DEVELOPMENT
# Role: Library — Package manager detection and operations
# ---------------------------------------------------------------------------
# Purpose:
# Used by:
# ---------------------------------------------------------------------------
# This file is a library. Source it — do not run it directly.
# ---------------------------------------------------------------------------
```
**Line limit override (add to header when applicable):**
```bash
# Line limit override: <concise reason>
```
---
*Created by John A. Hoeven with the ethical assistance of Claude AI*