1365 lines
No EOL
47 KiB
Markdown
1365 lines
No EOL
47 KiB
Markdown
# CE OS Script Style Guide — Bash
|
|
<!-- /ceos/CE_OS_ScriptStyleGuide_v0_0_3_EN.md -->
|
|
<!-- Version: v0.0.3 | Status: DEVELOPMENT -->
|
|
<!-- Created: 2026-05-27 | Updated: 2026-06-13 | Language: EN -->
|
|
<!-- Built standing on the shoulders of billions of dwarves -->
|
|
<!-- Created by John A. Hoeven with the ethical assistance of Claude AI -->
|
|
<!-- Licence: [The Unlicense](https://unlicense.org) -->
|
|
<!-- Commercial use is explicitly permitted. No conditions, no attribution
|
|
requirement, no notification needed. Do what you want with it. -->
|
|
<!-- We do ask, however, that you voluntarily attribute the dwarves — the
|
|
billions of contributors whose work made yours possible. It costs
|
|
nothing and means everything. -->
|
|
|
|
---
|
|
|
|
## 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 as a companion to this document. Python
|
|
scripts should follow this Bash guide as closely as the language permits;
|
|
deviations are documented in the Python guide with explicit justification.
|
|
|
|
These rules encode hard-won lessons from real hardware deployment across Alpine,
|
|
Debian, and NixOS on armv6/armhf, arm64, x86, and x86-64. 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 enthusiast-level reader — someone
|
|
eager to learn 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 both attributions in its header:
|
|
|
|
```
|
|
Built standing on the shoulders of billions of dwarves
|
|
Created by John A. Hoeven with the ethical assistance of Claude AI
|
|
```
|
|
|
|
The dwarves come first. 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 Licence
|
|
|
|
The default licence for all CE OS scripts is **The Unlicense**. Every script
|
|
header must declare its licence:
|
|
|
|
```bash
|
|
# Licence: The Unlicense — https://unlicense.org
|
|
|
|
Commercial use is explicitly permitted. No conditions, no attribution
|
|
requirement, no notification needed. Do what you want with it.
|
|
|
|
We do ask, however, that you voluntarily attribute the dwarves — the
|
|
billions of contributors whose work made yours possible. It costs
|
|
nothing and means everything.
|
|
```
|
|
|
|
Any deviation from The Unlicense requires an explicit explanation in the
|
|
script header:
|
|
|
|
```bash
|
|
# Licence: <licence name> — <URL>
|
|
# Licence note: <concise reason why The Unlicense is not used here>
|
|
```
|
|
|
|
A script with no licence declaration is incomplete.
|
|
|
|
### 1.4 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). No exceptions.
|
|
A script that installs packages without first updating the package index
|
|
is defective.
|
|
|
|
### 1.5 Architecture awareness
|
|
|
|
Every script that installs packages must:
|
|
|
|
1. Detect the running architecture (`uname -m`)
|
|
2. Consult `ce-index.conf` for known arch-specific availability
|
|
3. Test that each required package is available for that architecture
|
|
before attempting installation
|
|
4. Log clearly which packages are unavailable and why
|
|
5. Never silently skip a package — always surface the gap to the
|
|
OHIOD[^1].
|
|
|
|
### 1.6 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.7 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.8 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.9 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.10 Never run as root
|
|
|
|
CE OS scripts run as the OHIOD user and invoke the
|
|
detected privilege tool (`doas` or `sudo`) 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.
|
|
|
|
The privilege tool is detected by `bootstrap.sh` and written to
|
|
`ce_env.conf` as `CE_PRIV`. All scripts source this value — never
|
|
hardcode `doas` or `sudo`.
|
|
|
|
### 1.11 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; empty/blank lines are not included in the line count.
|
|
|
|
| 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.12 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: enthusiast level.** Make It Understandable and Obvious.
|
|
A competent but non-professional reader, eager to learn, 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**
|
|
`bootstrap.sh`, OpenRC service scripts, `/etc/local.d/` scripts, and
|
|
anything that runs before the CE OS environment is confirmed present.
|
|
Class A scripts are POSIX sh / ash throughout — no bash features permitted.
|
|
|
|
```sh
|
|
#!/bin/sh
|
|
# POSIX sh / ash only. No bash features.
|
|
```
|
|
|
|
**Class B — CE OS scripts**
|
|
All installer scripts, operational scripts, tier components, libraries,
|
|
and user-facing tools. Everything written for CE OS day-to-day use.
|
|
|
|
```bash
|
|
#!/usr/bin/env bash
|
|
# Bash features permitted. BusyBox tools on PATH on Alpine.
|
|
```
|
|
|
|
When in doubt, write Class B. Use Class A only when you have a specific
|
|
reason to require ash compatibility.
|
|
|
|
### 2.2 Full header block
|
|
|
|
Every CE OS Bash script carries this header in full:
|
|
|
|
```bash
|
|
#!/usr/bin/env bash
|
|
# Built standing on the shoulders of billions of dwarves
|
|
# Created by John A. Hoeven with the ethical assistance of Claude AI
|
|
# Licence: The Unlicense — https://unlicense.org
|
|
# ---------------------------------------------------------------------------
|
|
# <script-name>.sh
|
|
# /opt/ceos/scripts/<script-name>.sh
|
|
# Version: v0.0.1 | Status: DEVELOPMENT
|
|
# Role: <orchestrator | subscript | library>
|
|
# ---------------------------------------------------------------------------
|
|
# 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-lib.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
|
|
|
|
By the time any Class B script runs, `bootstrap.sh` has already detected
|
|
the package manager and written it to `ce_env.conf`. Class B scripts source
|
|
this file and load the correct package manager library — they do not detect
|
|
the package manager themselves.
|
|
|
|
```bash
|
|
# Source the bootstrap environment — always the first sourcing action
|
|
source /tmp/ce_env.conf || {
|
|
echo "ERROR: ce_env.conf not found. Was bootstrap.sh run first?" >&2
|
|
exit 1
|
|
}
|
|
|
|
# Load the correct package manager library for this distro
|
|
source "${LIB_DIR}/pkg/${CE_PKG_LIB}" || {
|
|
echo "ERROR: Package manager library not found: ${CE_PKG_LIB}" >&2
|
|
exit 1
|
|
}
|
|
```
|
|
|
|
Each package manager library (`lib/pkg/apk.sh`, `lib/pkg/apt.sh`, etc.)
|
|
exposes the same interface:
|
|
|
|
```bash
|
|
pkg_update() # Update package index
|
|
pkg_upgrade() # Upgrade installed packages
|
|
pkg_install() <pkg> # Install a package
|
|
pkg_available() <pkg> # Test availability — returns 0 if found
|
|
pkg_cache_clean() # Clear download cache
|
|
```
|
|
|
|
This means all higher-level scripts call `pkg_install vim` regardless of
|
|
distro — the library handles the distro-specific implementation.
|
|
|
|
### 2.7 Privilege tool abstraction
|
|
|
|
`bootstrap.sh` detects `doas` or `sudo` and writes `CE_PRIV` to
|
|
`ce_env.conf`. Never hardcode either tool:
|
|
|
|
```bash
|
|
# Correct — uses detected privilege tool
|
|
${CE_PRIV} pkg_install vim
|
|
|
|
# Wrong — assumes Alpine/CE OS default
|
|
doas pkg_install vim
|
|
|
|
# Wrong — assumes most other distros
|
|
sudo pkg_install vim
|
|
```
|
|
|
|
### 2.8 Init system abstraction
|
|
|
|
`bootstrap.sh` detects OpenRC or systemd and writes `CE_INIT` to
|
|
`ce_env.conf`. Load the correct init library:
|
|
|
|
```bash
|
|
source "${LIB_DIR}/init/${CE_INIT_LIB}" || {
|
|
echo "ERROR: Init library not found: ${CE_INIT_LIB}" >&2
|
|
exit 1
|
|
}
|
|
```
|
|
|
|
Each init library exposes the same interface:
|
|
|
|
```bash
|
|
svc_enable() <service> # Enable service at boot
|
|
svc_start() <service> # Start service now
|
|
svc_stop() <service> # Stop service
|
|
svc_restart() <service> # Restart service
|
|
svc_status() <service> # Query service status
|
|
```
|
|
|
|
### 2.9 Userland abstraction
|
|
|
|
`bootstrap.sh` detects BusyBox or GNU userland and writes `CE_USERLAND`
|
|
to `ce_env.conf`. For Class B scripts running on Alpine, prefer
|
|
BusyBox-safe patterns regardless — this keeps scripts portable across
|
|
both userlands without branching:
|
|
|
|
| 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` |
|
|
|
|
When a GNU-only feature is genuinely required and has no BusyBox-safe
|
|
equivalent, gate it on `CE_USERLAND`:
|
|
|
|
```bash
|
|
if [[ "${CE_USERLAND}" == "gnu" ]]; then
|
|
# GNU-specific implementation
|
|
else
|
|
# BusyBox-safe fallback
|
|
fi
|
|
```
|
|
|
|
Document the reason in a comment. Do not gate silently.
|
|
|
|
### 2.10 Architecture-aware package testing
|
|
|
|
Before installing any package, test availability for the running
|
|
architecture. The search order for Alpine is:
|
|
|
|
1. Consult `ce-index.conf` for known arch-specific status
|
|
2. Alpine stable (main)
|
|
3. Alpine community
|
|
4. Alpine edge
|
|
5. CE APK repo on Forgejo (`CE_APK_REPO`)
|
|
|
|
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}"
|
|
|
|
# Check index for known unavailability before hitting live repos
|
|
local index_key="CE_PKG_${pkg}_${arch}"
|
|
local index_val="${!index_key:-}"
|
|
if [[ "${index_val}" == "unavailable" ]]; then
|
|
log_warn " KNOWN UNAVAILABLE (index): ${pkg} on ${arch}"
|
|
return 1
|
|
fi
|
|
|
|
case "${PKG_MANAGER}" in
|
|
apk)
|
|
# Search official Alpine repos (stable, community, edge)
|
|
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}"
|
|
log_warn "If you believe this is incorrect, please file a bug report at:"
|
|
log_warn " ${CE_BUG_REPORT_URL}"
|
|
log_warn "Include: package=${pkg} arch=${ARCH} distro=${CE_DISTRO} scriptset=${CE_SCRIPTSET} date=$(date '+%Y-%m-%d')"
|
|
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 ! ${CE_PRIV} 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.11 Logging
|
|
|
|
Source `ce-common-lib.sh` for the standard logging functions. If writing
|
|
a standalone script that cannot source `ce-common-lib.sh`, implement
|
|
these four levels minimally:
|
|
|
|
```bash
|
|
# Minimal logging — use ce-common-lib.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`.
|
|
|
|
***Possible new logging scheme: /home/[user]/[subdirectory of choice]/[scriptset directory]/[script log time datestamp].log***
|
|
|
|
### 2.12 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
|
|
pkg_cache_clean 2>/dev/null || true
|
|
|
|
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.13 Test gates — structure
|
|
|
|
```bash
|
|
# ── Pre-flight ─────────────────────────────────────────────────────────────
|
|
|
|
phase_preflight() {
|
|
log_info "=== PRE-FLIGHT ==="
|
|
|
|
# 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 ==="
|
|
|
|
# vim is the CE OS default editor — required for all tiers
|
|
${CE_PRIV} 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.14 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 OHIOD Unit ID ${USER}"
|
|
exit 0 ;;
|
|
esac
|
|
}
|
|
```
|
|
|
|
### 2.15 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` and variant libraries)**
|
|
|
|
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` for shared libraries; variant libraries
|
|
live in domain subdirectories (`lib/pkg/apk.sh`, `lib/init/openrc.sh`).
|
|
|
|
```bash
|
|
#!/bin/sh
|
|
# Built standing on the shoulders of billions of dwarves
|
|
# Created by John A. Hoeven with the ethical assistance of Claude AI
|
|
# Licence: The Unlicense — https://unlicense.org
|
|
# ---------------------------------------------------------------------------
|
|
# 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: 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() { ... }
|
|
```
|
|
|
|
**Load-order dependencies in library subdirectories**
|
|
|
|
When libraries within a subdirectory depend on each other, numbered
|
|
prefixes (`01_`, `02_`) show load order. This is not sufficient
|
|
documentation on its own. The dependency must also be declared explicitly
|
|
in the `Depends:` header field of the dependent file:
|
|
|
|
```bash
|
|
# Depends: 01_core.sh — CE_PRIV and CE_USERLAND must be set before this loads
|
|
```
|
|
|
|
Numbered prefixes show order. The `Depends:` field explains why.
|
|
|
|
**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 `ce_env.conf` and 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 the bootstrap environment first — always
|
|
source /tmp/ce_env.conf || {
|
|
echo "ERROR: ce_env.conf not found. Was bootstrap.sh run first?" >&2
|
|
exit 1
|
|
}
|
|
|
|
# Resolve library directory 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
|
|
}
|
|
|
|
# Load variant libraries resolved by bootstrap
|
|
# shellcheck source=lib/pkg/apk.sh
|
|
source "${LIB_DIR}/pkg/${CE_PKG_LIB}" || {
|
|
echo "ERROR: Package library not found: ${CE_PKG_LIB}" >&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
|
|
|
|
```
|
|
ceos-alpine-installer/
|
|
├── bootstrap.sh # Class A — POSIX sh throughout
|
|
├── ce-install.sh # Orchestrator
|
|
├── ce-base.sh # Tier 0 subscript
|
|
├── ce-minimal.sh # Tier 1 subscript
|
|
├── ce-basic.sh # Tier 2 subscript
|
|
├── deploy.sh # Deployment wrapper
|
|
├── ce-manifest.conf # Script set target declaration
|
|
├── ce-index.conf # Arch/distro package and library index
|
|
├── lib/
|
|
│ ├── ce-common-lib.sh # Logging, run_test, confirm_proceed
|
|
│ ├── ce-test-lib.sh # Test gate framework
|
|
│ ├── base/ # Tier 0 component libraries
|
|
│ │ ├── 01_core.sh
|
|
│ │ └── 02_doas.sh
|
|
│ ├── minimal/ # Tier 1 component libraries
|
|
│ │ ├── 01_shell.sh
|
|
│ │ └── 02_terminal.sh
|
|
│ ├── basic/ # Tier 2 component libraries
|
|
│ │ ├── 01_comms.sh
|
|
│ │ └── 02_monitor.sh
|
|
│ ├── pkg/ # Package manager variant libraries
|
|
│ │ ├── apk.sh # Alpine
|
|
│ │ └── apt.sh # Debian
|
|
│ ├── init/ # Init system variant libraries
|
|
│ │ ├── openrc.sh # Alpine
|
|
│ │ └── systemd.sh # Debian
|
|
│ └── priv/ # Privilege tool variant libraries
|
|
│ ├── doas.sh # CE OS default
|
|
│ └── sudo.sh # Debian and others
|
|
├── spec/
|
|
│ └── install_spec.md # Human-readable install specification
|
|
└── tests/
|
|
├── test_base.sh
|
|
├── test_minimal.sh
|
|
└── test_basic.sh
|
|
```
|
|
|
|
#### Summary table
|
|
|
|
| Layer | Naming | Contains | Line limit |
|
|
|--------------|---------------------|----------------------------------|---------------------|
|
|
| Library | `ce-*-lib.sh` | Functions and constants only | 150 soft / 200 hard |
|
|
| Variant lib | `lib/<domain>/*.sh` | Distro-specific implementations | 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 |
|
|
|
|
### 2.16 Bootstrap — `bootstrap.sh`
|
|
|
|
`bootstrap.sh` is the sole Class A script in a CE OS installer set. It is
|
|
POSIX sh / ash throughout — no bash features, no CE OS libraries, no
|
|
sourcing of `ce_env.conf` (it creates it). It has one job per phase:
|
|
|
|
**Phase 1 — Ensure bash is present**
|
|
|
|
```sh
|
|
#!/bin/sh
|
|
# Built standing on the shoulders of billions of dwarves
|
|
# Created by John A. Hoeven with the ethical assistance of Claude AI
|
|
# Licence: The Unlicense — https://unlicense.org
|
|
# ---------------------------------------------------------------------------
|
|
# bootstrap.sh
|
|
# Version: v0.0.1 | Status: DEVELOPMENT
|
|
# Role: Bootstrap — Class A, POSIX sh throughout
|
|
# ---------------------------------------------------------------------------
|
|
# Purpose: Ensure bash is present, detect environment, write ce_env.conf,
|
|
# validate compatibility, then hand off to ce-install.sh.
|
|
# ---------------------------------------------------------------------------
|
|
|
|
command -v bash >/dev/null 2>&1 || {
|
|
echo "bash not found — installing..."
|
|
# Use whatever is available — no CE_PRIV yet
|
|
if command -v apk >/dev/null 2>&1; then
|
|
apk add bash
|
|
elif command -v apt-get >/dev/null 2>&1; then
|
|
apt-get install -y bash
|
|
else
|
|
echo "ERROR: Cannot install bash — no supported package manager found" >&2
|
|
exit 1
|
|
fi
|
|
}
|
|
```
|
|
|
|
**Phase 2 — Read the script set manifest**
|
|
|
|
```sh
|
|
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
|
. "${SCRIPT_DIR}/ce-manifest.conf" || {
|
|
echo "ERROR: ce-manifest.conf not found" >&2
|
|
exit 1
|
|
}
|
|
```
|
|
|
|
**Phase 3 — Detect environment**
|
|
|
|
```sh
|
|
# Distro
|
|
CE_DISTRO="unknown"
|
|
if [ -f /etc/os-release ]; then
|
|
. /etc/os-release
|
|
case "${ID:-}" in
|
|
alpine) CE_DISTRO="alpine" ;;
|
|
debian|ubuntu) CE_DISTRO="debian" ;;
|
|
nixos) CE_DISTRO="nixos" ;;
|
|
esac
|
|
fi
|
|
|
|
# Init system
|
|
CE_INIT="unknown"
|
|
command -v rc-service >/dev/null 2>&1 && CE_INIT="openrc"
|
|
command -v systemctl >/dev/null 2>&1 && CE_INIT="systemd"
|
|
|
|
# Privilege tool
|
|
CE_PRIV="unknown"
|
|
command -v doas >/dev/null 2>&1 && CE_PRIV="doas"
|
|
command -v sudo >/dev/null 2>&1 && CE_PRIV="sudo"
|
|
|
|
# Userland
|
|
CE_USERLAND="gnu"
|
|
ls --version 2>&1 | grep -q BusyBox && CE_USERLAND="busybox"
|
|
|
|
# Architecture
|
|
CE_ARCH="$(uname -m)"
|
|
```
|
|
|
|
**Phase 4 — Validate against manifest and hard fail if unsupported**
|
|
|
|
```sh
|
|
# Check distro compatibility
|
|
distro_supported=0
|
|
for d in ${CE_SUPPORTED_DISTROS}; do
|
|
[ "${CE_DISTRO}" = "${d}" ] && distro_supported=1 && break
|
|
done
|
|
|
|
if [ "${distro_supported}" -eq 0 ]; then
|
|
cat <<EOF
|
|
|
|
Cervello Elettrico has determined that this use case is best served by
|
|
${CE_DISTRO_TIER1} and can also be used with ${CE_DISTRO_TIER2} with
|
|
success, but other distros will need special attention that we are
|
|
unable to provide.
|
|
|
|
We recommend that you either install a compatible base distro, or modify
|
|
the scripts to suit your own choices. Our scripts are written to be read
|
|
and understood — not just run. If you are comfortable with the shell,
|
|
the comments are your map. Fork it, fix it, make it your own.
|
|
|
|
Detected distro: ${CE_DISTRO}
|
|
Script set: ${CE_SCRIPTSET}
|
|
|
|
EOF
|
|
exit 1
|
|
fi
|
|
```
|
|
|
|
**Phase 5 — Resolve variant library paths from index**
|
|
|
|
```sh
|
|
. "${SCRIPT_DIR}/ce-index.conf" || {
|
|
echo "ERROR: ce-index.conf not found" >&2
|
|
exit 1
|
|
}
|
|
|
|
# Resolve library paths for detected environment
|
|
eval "CE_PKG_LIB=\${CE_LIB_pkg_${CE_DISTRO}:-}"
|
|
eval "CE_INIT_LIB=\${CE_LIB_init_${CE_DISTRO}:-}"
|
|
eval "CE_PRIV_LIB=\${CE_LIB_priv_${CE_DISTRO}:-}"
|
|
|
|
[ -n "${CE_PKG_LIB}" ] || { echo "ERROR: No package library for ${CE_DISTRO}" >&2; exit 1; }
|
|
```
|
|
|
|
**Phase 6 — Write `ce_env.conf`**
|
|
|
|
```sh
|
|
cat > /tmp/ce_env.conf <<EOF
|
|
# CE OS environment — written by bootstrap.sh — do not edit manually
|
|
CE_SCRIPTSET="${CE_SCRIPTSET}"
|
|
CE_DISTRO="${CE_DISTRO}"
|
|
CE_INIT="${CE_INIT}"
|
|
CE_PRIV="${CE_PRIV}"
|
|
CE_USERLAND="${CE_USERLAND}"
|
|
CE_ARCH="${CE_ARCH}"
|
|
CE_PKG_LIB="${CE_PKG_LIB}"
|
|
CE_INIT_LIB="${CE_INIT_LIB}"
|
|
CE_PRIV_LIB="${CE_PRIV_LIB}"
|
|
CE_BUG_REPORT_URL="${CE_BUG_REPORT_URL}"
|
|
EOF
|
|
```
|
|
|
|
**Phase 7 — Hand off to orchestrator**
|
|
|
|
```sh
|
|
exec bash "${SCRIPT_DIR}/ce-install.sh" "$@"
|
|
```
|
|
|
|
`bootstrap.sh` does nothing else. All further logic belongs in the
|
|
orchestrator or its libraries.
|
|
|
|
### 2.17 Script set manifest — `ce-manifest.conf`
|
|
|
|
`ce-manifest.conf` sits at the root of each installer set alongside
|
|
`bootstrap.sh`. It is a POSIX sh-sourceable file declaring what the
|
|
script set supports and how to communicate its opinions.
|
|
|
|
```sh
|
|
# ce-manifest.conf — CE OS Workstation installer set
|
|
# Sourced by bootstrap.sh — POSIX sh only, no bash features
|
|
|
|
CE_SCRIPTSET="workstation"
|
|
|
|
# Tier 1: fully supported and tested
|
|
CE_DISTRO_TIER1="Alpine Linux"
|
|
# Tier 2: supported, minor issues possible
|
|
CE_DISTRO_TIER2="Debian"
|
|
|
|
# Machine-readable support lists (space-separated)
|
|
CE_SUPPORTED_DISTROS="alpine debian"
|
|
CE_UNSUPPORTED_DISTROS="nixos"
|
|
|
|
CE_SUPPORTED_ARCHES="aarch64 armhf x86_64"
|
|
CE_UNSUPPORTED_ARCHES="armv6 x86"
|
|
|
|
# Per-arch notes — surfaced on hard fail
|
|
CE_ARCH_NOTE_armv6="armv6 cannot run the AI stack or several Tier 2 packages"
|
|
CE_ARCH_NOTE_x86="x86 is missing several Tier 2 packages in Alpine repos"
|
|
|
|
# Bug report URL — Forgejo issue tracker for this script set
|
|
CE_BUG_REPORT_URL="[PLACEHOLDER]"
|
|
```
|
|
|
|
Cervello Elettrico is opinionated about targets — two supported tiers
|
|
plus the FFM path for everything else. This is more generous than most
|
|
projects, which support a single target. The manifest makes the opinion
|
|
explicit and gives the OHIOD everything they need to make an informed
|
|
choice.
|
|
|
|
### 2.18 Package and library index — `ce-index.conf`
|
|
|
|
`ce-index.conf` sits at the root of each installer set. It is a
|
|
POSIX sh-sourceable file declaring known package availability by arch
|
|
and library variant paths by distro.
|
|
|
|
```sh
|
|
# ce-index.conf — CE OS Workstation installer set
|
|
# Sourced by bootstrap.sh — POSIX sh only, no bash features
|
|
|
|
# Package availability index
|
|
# Format: CE_PKG_<pkgname>_<arch>="available|edge|ce-repo|unavailable"
|
|
# Only entries that deviate from 'available' need listing.
|
|
# Packages not listed are assumed available — the live search will confirm.
|
|
# When a package search fails unexpectedly, file a bug report so this
|
|
# index can be updated.
|
|
CE_PKG_glow_x86_64="edge"
|
|
CE_PKG_glow_aarch64="edge"
|
|
CE_PKG_glow_armhf="unavailable"
|
|
CE_PKG_glow_x86="unavailable"
|
|
|
|
# Library variant index
|
|
# Format: CE_LIB_<domain>_<distro>="filename.sh"
|
|
CE_LIB_pkg_alpine="apk.sh"
|
|
CE_LIB_pkg_debian="apt.sh"
|
|
CE_LIB_init_alpine="openrc.sh"
|
|
CE_LIB_init_debian="systemd.sh"
|
|
CE_LIB_priv_alpine="doas.sh"
|
|
CE_LIB_priv_debian="sudo.sh"
|
|
```
|
|
|
|
The index is the authoritative record of known deviations from standard
|
|
availability. The live package search is the fallback for anything not
|
|
listed. When a live search fails on a package that should be available,
|
|
that is the signal to file a bug report and update the index — one of
|
|
the few bug reports that directly improves the installer for everyone.
|
|
|
|
---
|
|
|
|
## Part 3 — Python Style Guide
|
|
|
|
CE OS Python scripts follow this Bash style guide as closely as the
|
|
language permits. The principles are identical: attribution headers,
|
|
test gates, cleanup on exit, no silent failures, credentials as
|
|
`[PLACEHOLDER]`, and scripts as teaching documents.
|
|
|
|
Where Python requires a different approach, the deviation is documented
|
|
here with an explicit justification. Do not invent deviations — if
|
|
something works the same way in Python as in Bash, do it the same way.
|
|
|
|
### 3.1 Attribution and licence header
|
|
|
|
Every Python script carries the same attribution as Bash, adapted for
|
|
Python comment syntax:
|
|
|
|
```python
|
|
#!/usr/bin/env python3
|
|
# Built standing on the shoulders of billions of dwarves
|
|
# Created by John A. Hoeven with the ethical assistance of Claude AI
|
|
# Licence: The Unlicense — https://unlicense.org
|
|
# ---------------------------------------------------------------------------
|
|
# script_name.py
|
|
# /opt/ceos/scripts/script_name.py
|
|
# Version: v0.0.1 | Status: DEVELOPMENT
|
|
# Role: <orchestrator | module | utility>
|
|
# ---------------------------------------------------------------------------
|
|
# Purpose: One-line description.
|
|
# Target: Which nodes / contexts (e.g. Alpine aarch64, Debian amd64)
|
|
# Entry: How invoked (e.g. python3 script_name.py --flag)
|
|
# Depends: List non-stdlib imports and their install source
|
|
# ---------------------------------------------------------------------------
|
|
```
|
|
|
|
### 3.2 Where Python deviates from Bash — and why
|
|
|
|
| Bash pattern | Python equivalent | Reason for deviation |
|
|
|---|---|---|
|
|
| `trap cleanup EXIT INT TERM` | `atexit.register()` + `try/finally` | Python has no `trap`; `atexit` is the idiomatic equivalent |
|
|
| `set -u` | Type hints + explicit checks | Python raises `NameError` on undefined names; `set -u` has no direct equivalent but type hints and explicit validation serve the same intent |
|
|
| `set -o pipefail` | `subprocess.run(check=True)` | Pipe failures are caught per-call in Python, not globally |
|
|
| `source library.sh` | `import ce_module` or `from ce_module import fn` | Python modules are the library equivalent; sourcing does not exist |
|
|
| `local var` | Variables scoped within functions naturally | Python function scope is implicit; `local` has no equivalent and is not needed |
|
|
| Orchestrator shell script | `main()` calling phase functions | Python orchestration lives in `main()` within a single file, or in a `__main__.py` for packages |
|
|
| `[[ condition ]]` | `if condition:` | Standard Python conditionals |
|
|
|
|
### 3.3 Mandatory patterns
|
|
|
|
**`main()` guard — always:**
|
|
|
|
```python
|
|
def main() -> None:
|
|
...
|
|
|
|
if __name__ == "__main__":
|
|
main()
|
|
```
|
|
|
|
Never put executable code at module level outside `main()` and constant
|
|
definitions. This makes the script safe to import for testing — the same
|
|
reason Bash libraries have no top-level executable code.
|
|
|
|
**Type hints — always:**
|
|
|
|
```python
|
|
def check_package_available(pkg: str, arch: str) -> bool:
|
|
...
|
|
```
|
|
|
|
Type hints serve the same intent as `${VAR:?message}` in Bash — they
|
|
document what a function expects and make failures explicit.
|
|
|
|
**Specific exception handling — always:**
|
|
|
|
```python
|
|
# Correct
|
|
try:
|
|
result = subprocess.run(cmd, check=True, capture_output=True, text=True)
|
|
except subprocess.CalledProcessError as e:
|
|
log.error(f"Command failed: {e.stderr.strip()}")
|
|
sys.exit(1)
|
|
|
|
# Wrong — never use bare except
|
|
try:
|
|
...
|
|
except:
|
|
pass
|
|
```
|
|
|
|
Bare `except` is the Python equivalent of swallowing errors silently.
|
|
It is a defect by the same rule as §1.8.
|
|
|
|
**Cleanup with `atexit`:**
|
|
|
|
```python
|
|
import atexit, shutil, tempfile
|
|
|
|
_tmp_dir: str | None = None
|
|
|
|
def _cleanup() -> None:
|
|
if _tmp_dir and os.path.isdir(_tmp_dir):
|
|
shutil.rmtree(_tmp_dir, ignore_errors=True)
|
|
log.info("Cleanup complete")
|
|
|
|
atexit.register(_cleanup)
|
|
```
|
|
|
|
Register cleanup before any work begins — same rule as the Bash trap.
|
|
|
|
**Line limits:** Python functions consume lines faster than Bash due to
|
|
type hints, docstrings, and explicit error handling. Line limits for
|
|
Python will be established by the dedicated Python style guide once
|
|
sufficient CE OS Python code exists to calibrate them from experience.
|
|
|
|
---
|
|
|
|
## 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: dwarves first, then John A. Hoeven / Claude AI
|
|
[ ] Licence declared (The Unlicense or deviation with explanation)
|
|
[ ] Version and Status in header
|
|
[ ] Script role declared (orchestrator / subscript / library)
|
|
[ ] Line limit override comment present if >200 lines (subscripts and libraries)
|
|
[ ] ce_env.conf sourced as first action (Class B scripts)
|
|
[ ] Correct variant libraries loaded via ce_env.conf paths
|
|
[ ] Package manager used via pkg_* interface, not directly
|
|
[ ] Privilege tool used via ${CE_PRIV}, never hardcoded
|
|
[ ] Init system used via svc_* interface, not directly
|
|
[ ] doas/sudo pkg_update && pkg_upgrade runs first (if installing packages)
|
|
[ ] Architecture detected (uname -m) before any package operations
|
|
[ ] ce-index.conf consulted before live package search
|
|
[ ] Each package tested: index → Alpine stable → community → edge → CE repo
|
|
[ ] Package unavailability logs bug report URL with full context
|
|
[ ] 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, pkg cache, 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 — ${CE_PRIV} 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
|
|
[ ] Load-order dependencies declared in Depends: field (not just numbered prefix)
|
|
```
|
|
|
|
### 4.2 Header templates — copy/paste
|
|
|
|
**Bootstrap (Class A — POSIX sh throughout):**
|
|
```sh
|
|
#!/bin/sh
|
|
# Built standing on the shoulders of billions of dwarves
|
|
# Created by John A. Hoeven with the ethical assistance of Claude AI
|
|
# Licence: The Unlicense — https://unlicense.org
|
|
# ---------------------------------------------------------------------------
|
|
# bootstrap.sh
|
|
# Version: v0.0.1 | Status: DEVELOPMENT
|
|
# Role: Bootstrap — Class A, POSIX sh throughout
|
|
# ---------------------------------------------------------------------------
|
|
# Purpose: Ensure bash present, detect environment, write ce_env.conf,
|
|
# validate compatibility, hand off to ce-install.sh.
|
|
# ---------------------------------------------------------------------------
|
|
```
|
|
|
|
**Orchestrator:**
|
|
```bash
|
|
#!/usr/bin/env bash
|
|
# Built standing on the shoulders of billions of dwarves
|
|
# Created by John A. Hoeven with the ethical assistance of Claude AI
|
|
# Licence: The Unlicense — https://unlicense.org
|
|
# ---------------------------------------------------------------------------
|
|
# 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-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
|
|
# Built standing on the shoulders of billions of dwarves
|
|
# Created by John A. Hoeven with the ethical assistance of Claude AI
|
|
# Licence: The Unlicense — https://unlicense.org
|
|
# ---------------------------------------------------------------------------
|
|
# 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
|
|
# Built standing on the shoulders of billions of dwarves
|
|
# Created by John A. Hoeven with the ethical assistance of Claude AI
|
|
# Licence: The Unlicense — https://unlicense.org
|
|
# ---------------------------------------------------------------------------
|
|
# 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>
|
|
```
|
|
|
|
---
|
|
|
|
[^1]: OHIOD — Organic Humanoid Input/Output Device. The person at the
|
|
keyboard. CE OS uses this term throughout to remind both the system and
|
|
its developers that the human is the principal, not the machine.
|
|
|
|
---
|
|
|
|
*Built standing on the shoulders of billions of dwarves*
|
|
|
|
*Created by John A. Hoeven with the ethical assistance of Claude AI*
|
|
|
|
*Licence: [The Unlicense](https://unlicense.org) — commercial use explicitly permitted.*
|
|
|
|
*We ask that you voluntarily attribute the dwarves.* |