# shellcheck shell=bash
# FlashOS shell helpers for Bash and Zsh.
#
# Source this file from the shell startup file, or use flashos.zsh for Zsh
# directory-based auto-source setups:
# source /path/to/FlashOS/flashos.sh
#
# Public interface:
# flashos help complete command overview
# flashos status|doctor orientation and environment readiness
# flashos build|run|smoke normal image development loop
# flashos recipe focused package/recipe development
# flashos artifacts|logs generated output inspection
# flashos check|qualify host gates and end-to-end qualification
# flashos changes|clean read-only Git inspection and maintenance
#
# Direct flashos-* functions expose the same wrappers. This file deliberately
# does not define generic build or run functions, perform Git writes, or write
# physical devices.
if [ -n "${BASH_VERSION:-}" ]; then
_flashos_source_path="${BASH_SOURCE[0]}"
_FLASHOS_DIR="$(CDPATH='' cd -- "$(dirname -- "$_flashos_source_path")" && pwd -P)"
elif [ -n "${ZSH_VERSION:-}" ]; then
eval '_flashos_source_path="${(%):-%x}"'
# Zsh's :A modifier resolves the sourced file without cd. That matters for
# directory-change auto-source hooks, where cd would recursively fire chpwd.
eval '_FLASHOS_DIR="${_flashos_source_path:A:h}"'
else
printf '%s\n' "flashos: flashos.sh supports Bash and Zsh" >&2
# The exit is the executed-file fallback.
# shellcheck disable=SC2317
return 1 2>/dev/null || exit 1
fi
unset _flashos_source_path
FLASHOS_ARCH="${FLASHOS_ARCH:-x86_64}"
FLASHOS_CONFIG_NAME="${FLASHOS_CONFIG_NAME:-flashos}"
_FLASHOS_AUTOMATION_BASELINE=134635a5e1282b5d8455a4b2aeb754be5a3a77c1
if [ -t 1 ] && [ -z "${NO_COLOR:-}" ]; then
_FLASHOS_RED="$(printf '\033[0;31m')"
_FLASHOS_GREEN="$(printf '\033[0;32m')"
_FLASHOS_YELLOW="$(printf '\033[1;33m')"
_FLASHOS_BLUE="$(printf '\033[0;34m')"
_FLASHOS_RESET="$(printf '\033[0m')"
else
_FLASHOS_RED=""
_FLASHOS_GREEN=""
_FLASHOS_YELLOW=""
_FLASHOS_BLUE=""
_FLASHOS_RESET=""
fi
_flashos_error() {
printf '%s\n' "${_FLASHOS_RED}flashos: $*${_FLASHOS_RESET}" >&2
}
_flashos_warn() {
printf '%s\n' "${_FLASHOS_YELLOW}flashos: $*${_FLASHOS_RESET}" >&2
}
_flashos_ok() {
printf '%s\n' "${_FLASHOS_GREEN}$*${_FLASHOS_RESET}"
}
_flashos_heading() {
printf '\n%s\n' "${_FLASHOS_BLUE}== $* ==${_FLASHOS_RESET}"
}
_flashos_root() {
(builtin cd -- "$_FLASHOS_DIR" && "$@")
}
_flash_root() {
(builtin cd -- "$_FLASHOS_DIR/components/flash" && "$@")
}
_flashos_make() {
_flashos_root command make \
"ARCH=$FLASHOS_ARCH" \
"CONFIG_NAME=$FLASHOS_CONFIG_NAME" \
"$@"
}
_flashos_acquire_automation() {
local platform
case "$(command uname -s)-$(command uname -m)" in
Darwin-arm64) platform=darwin-aarch64 ;;
Linux-x86_64) platform=linux-x86_64 ;;
*)
_flashos_error "public automation is unsupported on this host"
return 1
;;
esac
_flashos_root command make flash-bootstrap flash-automation-tools || return 1
_FLASHOS_AUTOMATION_RUNTIME="$_FLASHOS_DIR/build/flash-bootstrap/$_FLASHOS_AUTOMATION_BASELINE/fsh"
_FLASHOS_AUTOMATION_TOOLS="$_FLASHOS_DIR/build/flash-automation-tools/$platform/bin"
}
_flashos_run_automation() {
local script=$1
shift
_flashos_root command env \
"FLASH_AUTOMATION_RUNTIME=$_FLASHOS_AUTOMATION_RUNTIME" \
"FLASH_AUTOMATION_TAPLO=$_FLASHOS_AUTOMATION_TOOLS/taplo" \
"FLASH_AUTOMATION_JQ=$_FLASHOS_AUTOMATION_TOOLS/jq" \
"FLASH_AUTOMATION_RG=$_FLASHOS_AUTOMATION_TOOLS/rg" \
"$_FLASHOS_AUTOMATION_RUNTIME" "$script" "$@"
}
_flashos_build_dir() {
printf '%s\n' "$_FLASHOS_DIR/build/$FLASHOS_ARCH/$FLASHOS_CONFIG_NAME"
}
_flashos_require_file() {
if [ ! -f "$1" ]; then
_flashos_error "required file not found: $1"
return 1
fi
}
_flashos_no_arguments() {
local command_name=$1
shift
if [ "$#" -ne 0 ]; then
_flashos_error "flashos $command_name accepts no arguments"
return 1
fi
}
# -- Profile and image workflows -------------------------------------------
flashos-profile() {
local requested="${1:-}"
if [ "$#" -gt 1 ]; then
_flashos_error "flashos profile accepts at most one profile"
return 1
fi
if [ -z "$requested" ]; then
printf '%s\n' "$FLASHOS_CONFIG_NAME"
return 0
fi
case "$requested" in
dev) requested=flashos ;;
release) requested=flashos-release ;;
help|-h|--help)
printf '%s\n' "usage: flashos profile [dev|release|CONFIG_NAME]"
printf '%s\n' "current profile: $FLASHOS_CONFIG_NAME"
return 0
;;
esac
local manifest="$_FLASHOS_DIR/config/$FLASHOS_ARCH/$requested.toml"
if [ ! -f "$manifest" ]; then
_flashos_error "profile does not exist: ${manifest#"$_FLASHOS_DIR/"}"
return 1
fi
FLASHOS_CONFIG_NAME="$requested"
printf '%s\n' "FlashOS profile: $FLASHOS_CONFIG_NAME"
}
flashos-version() {
local version_file="$_FLASHOS_DIR/versions.env"
local version
_flashos_no_arguments version "$@" || return 1
_flashos_require_file "$version_file" || return 1
version="$(sed -n 's/^FLASHOS_RELEASE_VERSION=//p' "$version_file" | head -1)"
if [ -z "$version" ]; then
_flashos_error "FLASHOS_RELEASE_VERSION is missing from versions.env"
return 1
fi
printf '%s\n' "$version"
}
flashos-env() {
_flashos_make setenv "$@"
}
flashos-build() {
local mode=disk
case "${1:-}" in
disk|live|both|rebuild)
mode=$1
shift
;;
help|-h|--help)
printf '%s\n' "usage: flashos build [disk|live|both|rebuild] [make arguments]"
printf '%s\n' "profile: $FLASHOS_CONFIG_NAME arch: $FLASHOS_ARCH"
return 0
;;
""|-*) ;;
*)
_flashos_error "unknown build mode: $1 (expected disk, live, both, or rebuild)"
return 1
;;
esac
case "$mode" in
disk) _flashos_make all "$@" ;;
live) _flashos_make live "$@" ;;
both) _flashos_make all "$@" && _flashos_make live "$@" ;;
rebuild) _flashos_make rebuild "$@" ;;
esac
}
flashos-run() {
local mode=disk
case "${1:-}" in
disk|live)
mode=$1
shift
;;
help|-h|--help)
printf '%s\n' "usage: flashos run [disk|live] [make arguments]"
return 0
;;
""|-*) ;;
*)
_flashos_error "unknown run mode: $1 (expected disk or live)"
return 1
;;
esac
case "$mode" in
disk) _flashos_make qemu live=no "$@" ;;
live) _flashos_make qemu live=yes "$@" ;;
esac
}
_flashos_smoke_one() {
local mode=$1
local build_dir image interface log
shift
build_dir="$(_flashos_build_dir)"
case "$mode" in
disk)
image="$build_dir/harddrive.img"
interface=nvme
log="$build_dir/qemu-harddrive-smoke.log"
;;
live)
image="$build_dir/redox-live.iso"
interface=usb
log="$build_dir/qemu-live-usb-smoke.log"
;;
*)
_flashos_error "unknown smoke mode: $mode"
return 1
;;
esac
_flashos_require_file "$image" || {
_flashos_error "build it first with: flashos build $mode"
return 1
}
if [ "$FLASHOS_CONFIG_NAME" = flashos-release ]; then
_flashos_root command python3 ci/qemu_smoke.py \
--image "$image" \
--disk-interface "$interface" \
--log "$log" \
--expect-root-locked \
"$@"
else
_flashos_root command python3 ci/qemu_smoke.py \
--image "$image" \
--disk-interface "$interface" \
--log "$log" \
"$@"
fi
}
flashos-smoke() {
local mode="${1:-disk}"
[ "$#" -eq 0 ] || shift
case "$mode" in
disk) _flashos_smoke_one disk "$@" ;;
live) _flashos_smoke_one live "$@" ;;
all) _flashos_smoke_one disk "$@" && _flashos_smoke_one live "$@" ;;
help|-h|--help)
printf '%s\n' "usage: flashos smoke [disk|live|all] [qemu_smoke.py arguments]"
;;
*)
_flashos_error "unknown smoke mode: $mode (expected disk, live, or all)"
return 1
;;
esac
}
# -- Host and target quality gates -----------------------------------------
flash-check() {
local scope="${1:-all}"
[ "$#" -eq 0 ] || shift
case "$scope" in
fmt) _flash_root command cargo fmt --all --check "$@" ;;
clippy) _flash_root command cargo clippy --workspace --all-targets "$@" -- -D warnings ;;
test) _flash_root command cargo test --workspace --locked "$@" ;;
target) _flash_root command redoxer build -p flash-cli --bin fsh "$@" ;;
all)
_flash_root command cargo fmt --all --check "$@" &&
_flash_root command cargo clippy --workspace --all-targets -- -D warnings &&
_flash_root command cargo test --workspace --locked
;;
help|-h|--help)
printf '%s\n' "usage: flashos shell [fmt|clippy|test|target|all] [arguments]"
;;
*)
_flashos_error "unknown Flash check: $scope"
return 1
;;
esac
}
_flashos_check_shell_helpers() {
_flashos_acquire_automation &&
_flashos_run_automation ci/check_developer_interface.fsh &&
_flashos_root command python3 ci/check_public_automation.py
}
_flashos_check_quick() {
_flashos_check_shell_helpers &&
_flashos_run_automation ci/check_profile.fsh &&
_flashos_root command git diff --check
}
_flashos_check_python() {
_flashos_root command ruff check ci/ "$@" &&
_flashos_root command python3 -m unittest discover \
-s ci/tests -p 'test_*.py'
}
_flashos_check_root() {
_flashos_root command cargo fmt --all --check &&
_flashos_root command cargo test --locked
}
flashos-check() {
local scope="${1:-quick}"
[ "$#" -eq 0 ] || shift
case "$scope" in
quick) _flashos_check_quick ;;
profile)
_flashos_acquire_automation &&
_flashos_run_automation ci/check_profile.fsh "$@"
;;
root) _flashos_check_root ;;
shell) flash-check all "$@" ;;
python) _flashos_check_python "$@" ;;
docs) _flashos_check_docs ;;
target) flash-check target "$@" ;;
ci)
_flashos_check_quick &&
_flashos_check_root &&
flash-check all &&
_flashos_check_python
;;
all)
flashos-check ci && _flashos_check_docs
;;
help|-h|--help)
printf '%s\n' \
"usage: flashos check [quick|profile|root|shell|target|python|docs|ci|all]" \
"" \
" quick helper syntax, product profile, and whitespace" \
" root root Rust workspace formatting and tests" \
" shell Flash formatting, Clippy, and host tests" \
" target Flash Redox target build" \
" python lint FlashOS-owned Python" \
" docs private documentation drift check when available" \
" ci local equivalent of the host CI quality jobs" \
" all CI gates plus the private documentation drift check"
;;
*)
_flashos_error "unknown check scope: $scope"
return 1
;;
esac
}
# -- Development environment and version state -----------------------------
flashos-podman() {
local action="${1:-status}"
[ "$#" -eq 0 ] || shift
if ! command -v podman >/dev/null 2>&1; then
_flashos_error "podman is not installed"
return 1
fi
case "$action" in
status) command podman machine list "$@" ;;
start) command podman machine start "$@" ;;
stop) command podman machine stop "$@" ;;
info) command podman info "$@" ;;
help|-h|--help)
printf '%s\n' "usage: flashos podman [status|start|stop|info]"
;;
*)
_flashos_error "unknown Podman action: $action"
return 1
;;
esac
}
flashos-doctor() {
local missing=0 tool label
_flashos_no_arguments doctor "$@" || return 1
_flashos_heading "FlashOS development environment"
for tool in git make python3 cargo podman qemu-system-x86_64; do
case "$tool" in
qemu-system-x86_64) label="QEMU x86_64" ;;
*) label="$tool" ;;
esac
if command -v "$tool" >/dev/null 2>&1; then
printf '[ %sOK%s ] %-14s %s\n' \
"$_FLASHOS_GREEN" "$_FLASHOS_RESET" "$label" "$(command -v "$tool")"
else
printf '[ %sMISS%s ] %s\n' \
"$_FLASHOS_RED" "$_FLASHOS_RESET" "$label"
missing=1
fi
done
if command -v redoxer >/dev/null 2>&1; then
printf '[ %sOK%s ] %-14s %s\n' \
"$_FLASHOS_GREEN" "$_FLASHOS_RESET" redoxer "$(command -v redoxer)"
else
printf '[ %sOPT%s ] %-14s needed only for Flash target builds\n' \
"$_FLASHOS_YELLOW" "$_FLASHOS_RESET" redoxer
fi
if [ -f "$_FLASHOS_DIR/.config" ]; then
printf '[ %sOK%s ] %-14s %s\n' \
"$_FLASHOS_GREEN" "$_FLASHOS_RESET" configuration .config
else
printf '[ %sMISS%s ] %-14s copy the template from SETUP.md\n' \
"$_FLASHOS_RED" "$_FLASHOS_RESET" configuration
missing=1
fi
if [ -f /usr/share/OVMF/OVMF_CODE.fd ] ||
[ -f /usr/share/OVMF/OVMF_CODE_4M.fd ] ||
[ -f /usr/share/edk2/ovmf/OVMF_CODE.fd ] ||
[ -f /opt/homebrew/opt/qemu/share/qemu/edk2-x86_64-code.fd ]; then
printf '[ %sOK%s ] %-14s found\n' \
"$_FLASHOS_GREEN" "$_FLASHOS_RESET" OVMF/edk2
else
printf '[ %sMISS%s ] %-14s x86_64 firmware not found\n' \
"$_FLASHOS_RED" "$_FLASHOS_RESET" OVMF/edk2
missing=1
fi
if [ "$missing" -eq 0 ]; then
_flashos_ok "Environment ready"
else
_flashos_error "environment is incomplete; see SETUP.md"
return 1
fi
}
flashos-versions() {
local action="${1:-show}" version expected_tag newest_tag description distance
[ "$#" -eq 0 ] || shift
case "$action" in
show)
[ "$#" -eq 0 ] || {
_flashos_error "flashos versions show accepts no arguments"
return 1
}
version="$(flashos-version)" || return 1
expected_tag="v$version"
newest_tag="$(_flashos_root command git describe --tags --abbrev=0 2>/dev/null || true)"
description="$(_flashos_root command git describe --tags --always --dirty 2>/dev/null || true)"
printf '%s\n' "product version: $version"
printf '%s\n' "release tag: $expected_tag"
printf '%s\n' "checkout: ${description:-unknown}"
if [ -n "$newest_tag" ]; then
distance="$(_flashos_root command git rev-list --count "$newest_tag"..HEAD)" || return 1
printf '%s\n' "newest tag: $newest_tag ($distance commits behind HEAD)"
else
printf '%s\n' "newest tag: none"
fi
;;
check)
[ "$#" -eq 0 ] || {
_flashos_error "flashos versions check accepts no arguments"
return 1
}
_flashos_acquire_automation &&
_flashos_run_automation ci/check_profile.fsh
;;
help|-h|--help)
printf '%s\n' "usage: flashos versions [show|check]"
;;
*)
_flashos_error "unknown versions action: $action"
return 1
;;
esac
}
# -- Recipe development -----------------------------------------------------
_flashos_recipe_name() {
case "$1" in
"")
_flashos_error "a recipe name is required"
return 1
;;
*[!A-Za-z0-9_+.,-]*)
_flashos_error "invalid recipe list: $1"
return 1
;;
esac
}
flashos-recipe() {
local action="${1:-help}" recipes="${2:-}"
[ "$#" -eq 0 ] || shift
[ "$#" -eq 0 ] || shift
case "$action" in
tree)
if [ -n "$recipes" ]; then
_flashos_recipe_name "$recipes" || return 1
_flashos_make "rt.$recipes" "$@"
else
_flashos_make repo-tree "$@"
fi
;;
image-tree)
if [ -n "$recipes" ]; then
_flashos_recipe_name "$recipes" || return 1
_flashos_make "pt.$recipes" "$@"
else
_flashos_make image-tree "$@"
fi
;;
find|fetch|build|rebuild|clean|unfetch|push|build-push|rebuild-push)
_flashos_recipe_name "$recipes" || return 1
case "$action" in
find) _flashos_make "find.$recipes" "$@" ;;
fetch) _flashos_make "f.$recipes" "$@" ;;
build) _flashos_make "r.$recipes" "$@" ;;
rebuild) _flashos_make "cr.$recipes" "$@" ;;
clean) _flashos_make "c.$recipes" "$@" ;;
unfetch) _flashos_make "u.$recipes" "$@" ;;
push)
_flashos_warn "stop QEMU before modifying an image"
_flashos_make "p.$recipes" "$@"
;;
build-push)
_flashos_warn "stop QEMU before modifying an image"
_flashos_make "rp.$recipes" "$@"
;;
rebuild-push)
_flashos_warn "stop QEMU before modifying an image"
_flashos_make "crp.$recipes" "$@"
;;
esac
;;
help|-h|--help)
printf '%s\n' \
"usage: flashos recipe <action> [recipe[,recipe...]] [make arguments]" \
"" \
" find NAME locate a recipe" \
" tree [NAME] show the configured cook tree" \
" image-tree [NAME] show the image push tree" \
" fetch NAME fetch recipe sources" \
" build NAME cook a recipe" \
" rebuild NAME clean and cook a recipe" \
" clean NAME clean recipe outputs" \
" unfetch NAME remove fetched recipe sources" \
" push NAME push a built package into the image" \
" build-push NAME cook and push a package" \
" rebuild-push NAME clean, cook, and push a package"
;;
*)
_flashos_error "unknown recipe action: $action"
return 1
;;
esac
}
# -- Artifacts, logs, and source inspection --------------------------------
_flashos_artifact_path() {
local build_dir
build_dir="$(_flashos_build_dir)"
case "$1" in
disk) printf '%s\n' "$build_dir/harddrive.img" ;;
live) printf '%s\n' "$build_dir/redox-live.iso" ;;
*)
_flashos_error "unknown artifact: $1 (expected disk or live)"
return 1
;;
esac
}
_flashos_hash_file() {
if command -v sha256sum >/dev/null 2>&1; then
command sha256sum "$1"
elif command -v shasum >/dev/null 2>&1; then
command shasum -a 256 "$1"
else
_flashos_error "sha256sum or shasum is required"
return 1
fi
}
flashos-artifacts() {
local action="${1:-list}" artifact path build_dir size
[ "$#" -eq 0 ] || shift
build_dir="$(_flashos_build_dir)"
case "$action" in
list)
[ "$#" -eq 0 ] || {
_flashos_error "flashos artifacts list accepts no arguments"
return 1
}
for artifact in disk live; do
path="$(_flashos_artifact_path "$artifact")" || return 1
if [ -f "$path" ]; then
size="$(command du -h "$path" | awk '{print $1}')"
printf '%-5s %-6s %s\n' "$artifact" "$size" "${path#"$_FLASHOS_DIR/"}"
else
printf '%-5s %-6s %s\n' "$artifact" missing "${path#"$_FLASHOS_DIR/"}"
fi
done
;;
path)
artifact="${1:-disk}"
_flashos_artifact_path "$artifact"
;;
hash)
artifact="${1:-all}"
case "$artifact" in
all)
for artifact in disk live; do
path="$(_flashos_artifact_path "$artifact")" || return 1
_flashos_require_file "$path" || return 1
_flashos_hash_file "$path" || return 1
done
;;
disk|live)
path="$(_flashos_artifact_path "$artifact")" || return 1
_flashos_require_file "$path" || return 1
_flashos_hash_file "$path"
;;
*)
_flashos_error "unknown artifact: $artifact"
return 1
;;
esac
;;
help|-h|--help)
printf '%s\n' "usage: flashos artifacts [list|path|hash] [disk|live|all]"
;;
*)
_flashos_error "unknown artifacts action: $action"
return 1
;;
esac
}
_flashos_log_path() {
local build_dir
build_dir="$(_flashos_build_dir)"
case "$1" in
disk) printf '%s\n' "$build_dir/qemu-harddrive-smoke.log" ;;
live) printf '%s\n' "$build_dir/qemu-live-usb-smoke.log" ;;
*)
_flashos_error "unknown log: $1 (expected disk or live)"
return 1
;;
esac
}
flashos-logs() {
local action="${1:-list}" mode="${2:-disk}" path build_dir
[ "$#" -eq 0 ] || shift
[ "$#" -eq 0 ] || shift
build_dir="$(_flashos_build_dir)"
case "$action" in
list)
command find "$build_dir" -maxdepth 1 -type f -name '*.log' -print 2>/dev/null | command sort
;;
disk|live)
path="$(_flashos_log_path "$action")" || return 1
_flashos_require_file "$path" || return 1
command tail -n "${FLASHOS_LOG_LINES:-80}" "$path"
;;
follow)
path="$(_flashos_log_path "$mode")" || return 1
_flashos_require_file "$path" || return 1
command tail -n "${FLASHOS_LOG_LINES:-80}" -f "$path"
;;
help|-h|--help)
printf '%s\n' "usage: flashos logs [list|disk|live|follow] [disk|live]"
printf '%s\n' "set FLASHOS_LOG_LINES to change the default tail length (80)"
;;
*)
_flashos_error "unknown logs action: $action"
return 1
;;
esac
}
flashos-changes() {
local action="${1:-status}"
[ "$#" -eq 0 ] || shift
case "$action" in
status) _flashos_root command git status --short --branch "$@" ;;
diff) _flashos_root command git diff "$@" ;;
stat) _flashos_root command git diff --stat "$@" ;;
staged) _flashos_root command git diff --cached "$@" ;;
recent) _flashos_root command git log --oneline --decorate -n "${1:-12}" ;;
help|-h|--help)
printf '%s\n' "usage: flashos changes [status|diff|stat|staged|recent]"
printf '%s\n' "this interface is read-only; it never commits, pushes, or tags"
;;
*)
_flashos_error "unknown changes action: $action"
return 1
;;
esac
}
# -- Maintenance and end-to-end qualification ------------------------------
flashos-clean() {
local scope="${1:-help}"
[ "$#" -eq 0 ] || shift
case "$scope" in
build)
_flashos_warn "removing generated build, prefix, repository, and fstools state"
_flashos_make clean "$@"
;;
recipes) _flashos_make repo_clean "$@" ;;
fetches) _flashos_make fetch_clean "$@" ;;
container)
_flashos_warn "removing the local FlashOS container image and state"
_flashos_make container_clean "$@"
;;
dist)
_flashos_warn "removing all fetched and generated build state"
_flashos_make distclean "$@"
;;
help|-h|--help)
printf '%s\n' \
"usage: flashos clean <build|recipes|fetches|container|dist>" \
"" \
"No cleanup runs without an explicit scope. 'dist' is the broadest."
;;
*)
_flashos_error "unknown clean scope: $scope"
return 1
;;
esac
}
_flashos_check_docs() {
local _drift_dir="${FLASHOS_PRIVATE_DIR:-${_FLASHOS_DIR}/.private}"
local _drift_script="$_drift_dir/scripts/check_drift.sh"
_flashos_acquire_automation || return 1
_flashos_root command python3 ci/check_public_automation.py \
--documentation-only \
--documentation-runtime "$_FLASHOS_AUTOMATION_RUNTIME" || return 1
if [ -x "$_drift_script" ]; then
_flashos_root command "$_drift_script"
else
_flashos_warn "private documentation drift checker is not present"
fi
}
flashos-qualify() {
local mode="${1:-all}"
[ "$#" -le 1 ] || {
_flashos_error "flashos qualify accepts one mode"
return 1
}
case "$mode" in
disk|live|all) ;;
help|-h|--help)
printf '%s\n' "usage: flashos qualify [disk|live|all]"
printf '%s\n' "runs all local quality gates, builds, then smokes exact artifacts"
return 0
;;
*)
_flashos_error "unknown qualification mode: $mode"
return 1
;;
esac
_flashos_heading "Quality gates"
flashos-check all || return 1
_flashos_heading "Image build ($mode)"
if [ "$mode" = all ]; then
flashos-build both || return 1
else
flashos-build "$mode" || return 1
fi
_flashos_heading "Exact-artifact runtime ($mode)"
flashos-smoke "$mode" || return 1
_flashos_ok "FlashOS $mode qualification passed"
}
# -- Status, discovery, and dispatcher -------------------------------------
flashos-status() {
local build_dir version artifact size
_flashos_no_arguments status "$@" || return 1
build_dir="$(_flashos_build_dir)"
version="$(flashos-version)" || return 1
printf '%s\n' "repository: $_FLASHOS_DIR"
printf '%s\n' "version: $version"
printf '%s\n' "profile: $FLASHOS_CONFIG_NAME"
printf '%s\n' "arch: $FLASHOS_ARCH"
printf '%s\n\n' "build dir: ${build_dir#"$_FLASHOS_DIR/"}"
_flashos_root command git status --short --branch
for artifact in "$build_dir/harddrive.img" "$build_dir/redox-live.iso"; do
if [ -f "$artifact" ]; then
size="$(command du -h "$artifact" | awk '{print $1}')"
printf '%s\n' "artifact: ${artifact#"$_FLASHOS_DIR/"} $size"
else
printf '%s\n' "artifact: ${artifact#"$_FLASHOS_DIR/"} (not built)"
fi
done
}
flashos-list() {
_flashos_no_arguments list "$@" || return 1
_flashos_usage
_flashos_heading "Direct helper functions"
command grep -E '^(flashos(-[[:alnum:]-]+)?|flash-check|fos)\(\)' \
"$_FLASHOS_DIR/flashos.sh" | command sed 's/().*//' | command sort -u
}
_flashos_usage() {
printf '%s\n' \
"usage: flashos <command> [arguments]" \
"" \
"Orientation" \
" status show repository, profile, and artifacts" \
" doctor validate the development environment" \
" version print the product version" \
" versions [show|check] show release and tag state or check drift" \
" profile [dev|release] show or select the image profile" \
" env [make arguments] print the selected Make environment" \
"" \
"Build and runtime" \
" build [disk|live|both] build one or both images" \
" run [disk|live] run interactive QEMU" \
" smoke [disk|live|all] smoke-test already built artifacts" \
" qualify [disk|live|all] check, build, and smoke end to end" \
" recipe <action> [name] find, cook, rebuild, or push recipes" \
"" \
"Inspection and quality" \
" artifacts [action] list, locate, or hash image artifacts" \
" logs [action] inspect or follow QEMU smoke logs" \
" changes [action] inspect Git state without writing it" \
" check [scope] run repository checks" \
" shell [scope] run Flash host/target checks" \
"" \
"Maintenance" \
" podman [action] inspect or control the Podman machine" \
" clean <scope> remove an explicit generated-data scope" \
" root change to the repository root" \
" list show commands and direct helper functions" \
" help show this overview" \
"" \
"Commands with modes, actions, or scopes accept command-specific help."
}
flashos() {
local command_name="${1:-help}"
[ "$#" -eq 0 ] || shift
case "$command_name" in
status) flashos-status "$@" ;;
doctor) flashos-doctor "$@" ;;
version) flashos-version "$@" ;;
versions) flashos-versions "$@" ;;
profile) flashos-profile "$@" ;;
env) flashos-env "$@" ;;
build) flashos-build "$@" ;;
run) flashos-run "$@" ;;
smoke) flashos-smoke "$@" ;;
qualify) flashos-qualify "$@" ;;
recipe) flashos-recipe "$@" ;;
artifacts) flashos-artifacts "$@" ;;
logs) flashos-logs "$@" ;;
changes) flashos-changes "$@" ;;
check) flashos-check "$@" ;;
shell) flash-check "$@" ;;
podman) flashos-podman "$@" ;;
clean) flashos-clean "$@" ;;
root)
_flashos_no_arguments root "$@" || return 1
builtin cd -- "$_FLASHOS_DIR" || return 1
;;
list) flashos-list "$@" ;;
help|-h|--help)
_flashos_no_arguments help "$@" || return 1
_flashos_usage
;;
*)
_flashos_error "unknown command: $command_name"
_flashos_usage >&2
return 1
;;
esac
}
fos() {
flashos "$@"
}
if [ -n "${BASH_VERSION:-}" ] && command -v complete >/dev/null 2>&1; then
_flashos_bash_completion() {
local current previous choices
current="${COMP_WORDS[COMP_CWORD]}"
previous="${COMP_WORDS[COMP_CWORD-1]}"
if [ "$COMP_CWORD" -eq 1 ]; then
choices="status doctor version versions profile env build run smoke qualify recipe artifacts logs changes check shell podman clean root list help"
else
case "$previous" in
profile) choices="dev release" ;;
versions) choices="show check" ;;
build) choices="disk live both rebuild" ;;
run) choices="disk live" ;;
smoke) choices="disk live all" ;;
qualify) choices="disk live all" ;;
recipe) choices="find tree image-tree fetch build rebuild clean unfetch push build-push rebuild-push" ;;
artifacts) choices="list path hash" ;;
logs) choices="list disk live follow" ;;
changes) choices="status diff stat staged recent" ;;
check) choices="quick profile root shell target python docs ci all" ;;
shell) choices="fmt clippy test target all" ;;
podman) choices="status start stop info" ;;
clean) choices="build recipes fetches container dist" ;;
*) choices="" ;;
esac
fi
# Word splitting is intentional: compgen consumes the choice list. This
# stays compatible with the Bash 3.2 still shipped by macOS.
# shellcheck disable=SC2207
COMPREPLY=($(compgen -W "$choices" -- "$current"))
}
complete -F _flashos_bash_completion flashos fos
fi
Return to documentation →