Add bash styleguide
This commit is contained in:
parent
ee6cc103fe
commit
287924c066
1 changed files with 852 additions and 0 deletions
852
bash-styleguide
Normal file
852
bash-styleguide
Normal 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*
|
||||
Loading…
Add table
Add a link
Reference in a new issue