first commit
This commit is contained in:
739
lib.sh
Executable file
739
lib.sh
Executable file
@@ -0,0 +1,739 @@
|
||||
#!/usr/bin/env bash
|
||||
# Bibliotheque centrale
|
||||
|
||||
if [[ "${NETBIAN_LIB_SOURCED:-}" == "1" ]]; then
|
||||
return 0 2>/dev/null || exit 0
|
||||
fi
|
||||
NETBIAN_LIB_SOURCED=1
|
||||
readonly NETBIAN_LIB_SOURCED
|
||||
|
||||
###############################################################################
|
||||
### Helpers generaux
|
||||
#
|
||||
|
||||
enable_strict_mode() {
|
||||
set -Eeuo pipefail
|
||||
}
|
||||
|
||||
err() { echo "$@" >&2; }
|
||||
errln() { printf '%s\n' "$*" >&2; }
|
||||
fatal() {
|
||||
err "$@"
|
||||
exit 2
|
||||
}
|
||||
|
||||
exists_cmd() {
|
||||
command -v "$1" >/dev/null 2>&1
|
||||
}
|
||||
|
||||
safe_tempfile() {
|
||||
mktemp --tmpdir netbian.tmp.XXXXXX 2>/dev/null || mktemp /tmp/netbian.tmp.XXXXXX
|
||||
}
|
||||
|
||||
require_debian_version() {
|
||||
local requested="${1:-}"
|
||||
[[ -n "$requested" ]] || fatal "Missing required Debian version argument."
|
||||
[[ -r /etc/os-release ]] || fatal "/etc/os-release not found or not readable."
|
||||
|
||||
. /etc/os-release
|
||||
[[ "${ID:-}" == "debian" ]] || fatal "Not Debian (ID=${ID:-})."
|
||||
|
||||
case "${VERSION_ID:-}" in
|
||||
"$requested" | "$requested".*) return 0 ;;
|
||||
*) fatal "You are using Debian ${VERSION_ID:-}, Debian ${requested} is required." ;;
|
||||
esac
|
||||
}
|
||||
|
||||
require_valid_name() {
|
||||
local kind="$1" value="$2"
|
||||
[[ -n "$value" ]] || fatal "Missing ${kind}."
|
||||
[[ "$value" =~ ^[a-z0-9][a-z0-9_-]*$ ]] || fatal "Invalid ${kind}: ${value}"
|
||||
}
|
||||
|
||||
validate_config_file() {
|
||||
local cfg="$1" line lineno=0 key value
|
||||
[[ -f "$cfg" ]] || return 0
|
||||
|
||||
while IFS= read -r line || [[ -n "$line" ]]; do
|
||||
lineno=$((lineno + 1))
|
||||
[[ -z "$line" ]] && continue
|
||||
[[ "$line" =~ ^[[:space:]]*# ]] && continue
|
||||
if [[ ! "$line" =~ ^[A-Za-z_][A-Za-z0-9_]*= ]]; then
|
||||
fatal "Invalid config line ${cfg}:${lineno}: ${line}"
|
||||
fi
|
||||
key="${line%%=*}"
|
||||
value="${line#*=}"
|
||||
case "$key" in
|
||||
profile) require_valid_name "profile" "$value" ;;
|
||||
lang)
|
||||
[[ -z "$value" || "$value" =~ ^[a-z]{2}([-_][a-z]{2})?$ ]] || fatal "Invalid language code in ${cfg}:${lineno}: ${value}"
|
||||
;;
|
||||
esac
|
||||
done <"$cfg"
|
||||
}
|
||||
|
||||
###############################################################################
|
||||
### Logging and execution summary
|
||||
#
|
||||
|
||||
log_run() { printf '[RUN] %s\n' "$*"; }
|
||||
log_ok() { printf '[OK] %s\n' "$*"; }
|
||||
log_skip() {
|
||||
NETBIAN_SKIP_EVENTS=$(( ${NETBIAN_SKIP_EVENTS:-0} + 1 ))
|
||||
printf '[SKIP] %s\n' "$*"
|
||||
}
|
||||
log_warn() { printf '[WARN] %s\n' "$*"; }
|
||||
log_info() { printf '[INFO] %s\n' "$*"; }
|
||||
|
||||
# Runtime flags
|
||||
NETBIAN_DRY_RUN="${NETBIAN_DRY_RUN:-0}"
|
||||
NETBIAN_DEBUG="${NETBIAN_DEBUG:-0}"
|
||||
|
||||
# Execution summary
|
||||
if [[ -z "${NETBIAN_SUMMARY_INITIALIZED:-}" ]]; then
|
||||
declare -ag EXECUTED_STEPS=()
|
||||
declare -ag SKIPPED_STEPS=()
|
||||
declare -ag EXECUTED_ROLES=()
|
||||
declare -ag FAILED_STEPS=()
|
||||
NETBIAN_SKIP_EVENTS=0
|
||||
NETBIAN_SUMMARY_INITIALIZED=1
|
||||
fi
|
||||
|
||||
debug_enabled() {
|
||||
[[ "${NETBIAN_DEBUG:-0}" == "1" ]]
|
||||
}
|
||||
|
||||
dry_run_enabled() {
|
||||
[[ "${NETBIAN_DRY_RUN:-0}" == "1" ]]
|
||||
}
|
||||
|
||||
record_executed_step() {
|
||||
EXECUTED_STEPS+=("$1")
|
||||
}
|
||||
|
||||
record_skipped_step() {
|
||||
SKIPPED_STEPS+=("$1")
|
||||
}
|
||||
|
||||
record_failed_step() {
|
||||
FAILED_STEPS+=("$1")
|
||||
}
|
||||
|
||||
record_executed_role() {
|
||||
EXECUTED_ROLES+=("$1")
|
||||
}
|
||||
|
||||
print_execution_summary() {
|
||||
local item
|
||||
echo
|
||||
echo "==== Execution summary ===="
|
||||
printf 'Roles executed : %d\n' "${#EXECUTED_ROLES[@]}"
|
||||
printf 'Steps executed : %d\n' "${#EXECUTED_STEPS[@]}"
|
||||
printf 'Steps skipped : %d\n' "${#SKIPPED_STEPS[@]}"
|
||||
printf 'Skip events : %d\n' "${NETBIAN_SKIP_EVENTS:-0}"
|
||||
printf 'Steps failed : %d\n' "${#FAILED_STEPS[@]}"
|
||||
|
||||
if ((${#EXECUTED_ROLES[@]} > 0)); then
|
||||
echo "Executed roles:"
|
||||
for item in "${EXECUTED_ROLES[@]}"; do
|
||||
printf ' - %s\n' "$item"
|
||||
done
|
||||
fi
|
||||
|
||||
if ((${#SKIPPED_STEPS[@]} > 0)); then
|
||||
echo "Skipped steps:"
|
||||
for item in "${SKIPPED_STEPS[@]}"; do
|
||||
printf ' - %s\n' "$item"
|
||||
done
|
||||
fi
|
||||
|
||||
if ((${#FAILED_STEPS[@]} > 0)); then
|
||||
echo "Failed steps:"
|
||||
for item in "${FAILED_STEPS[@]}"; do
|
||||
printf ' - %s\n' "$item"
|
||||
done
|
||||
fi
|
||||
}
|
||||
|
||||
###############################################################################
|
||||
### Gestion de fichiers
|
||||
#
|
||||
|
||||
write_if_changed() {
|
||||
local src="$1" dst="$2"
|
||||
|
||||
if [[ -f "$dst" ]] && cmp -s "$src" "$dst"; then
|
||||
rm -f "$src"
|
||||
return 1
|
||||
fi
|
||||
|
||||
mv "$src" "$dst"
|
||||
chmod 0644 "$dst"
|
||||
return 0
|
||||
}
|
||||
|
||||
write_text_file_if_changed() {
|
||||
local content="$1" dst="$2" mode="${3:-0644}" tmp
|
||||
install -d -m0755 "$(dirname "$dst")"
|
||||
tmp="$(safe_tempfile)" || tmp="/tmp/netbian.write.$$"
|
||||
printf '%s' "$content" >"$tmp"
|
||||
if write_if_changed "$tmp" "$dst"; then
|
||||
chmod "$mode" "$dst"
|
||||
return 0
|
||||
fi
|
||||
return 1
|
||||
}
|
||||
|
||||
copy_config() {
|
||||
local rel="$1" dest_dir="$2" src file dest user rootname
|
||||
|
||||
[[ -n "$rel" && -n "$dest_dir" ]] || {
|
||||
echo "Usage: copy_config <relpath> <dest_dir>" >&2
|
||||
return 2
|
||||
}
|
||||
|
||||
src="$PROJECT_DIR/config/$rel"
|
||||
[[ -f "$src" ]] || {
|
||||
log_warn "$src not found - skipping"
|
||||
return 0
|
||||
}
|
||||
|
||||
file="$(basename "$rel")"
|
||||
dest="$(realpath -m "$dest_dir")/$file"
|
||||
install -d "$(dirname "$dest")"
|
||||
|
||||
rootname="${rel%%/*}"
|
||||
rootname="$(tr '[:lower:]' '[:upper:]' <<<"${rootname:0:1}")${rootname:1}"
|
||||
if [[ ! -f "$dest" ]] || ! cmp -s "$src" "$dest"; then
|
||||
log_info "${rootname} configuration copied"
|
||||
cp -a "$src" "$dest"
|
||||
else
|
||||
log_skip "${rootname} already configured"
|
||||
fi
|
||||
|
||||
if [[ "$dest" =~ ^/home/([^/]+)/ ]]; then
|
||||
user="${BASH_REMATCH[1]}"
|
||||
if id -u "$user" >/dev/null 2>&1; then
|
||||
chown "$user:$user" "$dest"
|
||||
chmod 0664 "$dest"
|
||||
return 0
|
||||
fi
|
||||
fi
|
||||
|
||||
chown root:root "$dest"
|
||||
chmod 0644 "$dest"
|
||||
return 0
|
||||
}
|
||||
|
||||
###############################################################################
|
||||
### Paquets / APT
|
||||
#
|
||||
|
||||
ensure_packages_installed() {
|
||||
local to_install=() p prev_deb
|
||||
for p in "$@"; do
|
||||
if ! dpkg-query -W -f='${Status}' "$p" 2>/dev/null | grep -q '^install ok installed$'; then
|
||||
to_install+=("$p")
|
||||
fi
|
||||
done
|
||||
|
||||
if ((${#to_install[@]} == 0)); then
|
||||
log_skip "packages already installed"
|
||||
return 3
|
||||
fi
|
||||
|
||||
prev_deb="${DEBIAN_FRONTEND:-}"
|
||||
export DEBIAN_FRONTEND=noninteractive
|
||||
if ! apt-get install -y "${to_install[@]}"; then
|
||||
if [[ -n "$prev_deb" ]]; then
|
||||
export DEBIAN_FRONTEND="$prev_deb"
|
||||
else
|
||||
unset DEBIAN_FRONTEND
|
||||
fi
|
||||
echo "Failed to install packages: ${to_install[*]}" >&2
|
||||
return 1
|
||||
fi
|
||||
|
||||
if [[ -n "$prev_deb" ]]; then
|
||||
export DEBIAN_FRONTEND="$prev_deb"
|
||||
else
|
||||
unset DEBIAN_FRONTEND
|
||||
fi
|
||||
return 0
|
||||
}
|
||||
|
||||
add_apt_key_from_url() {
|
||||
local key_url="$1" keyring_path="$2" tmpkey tmpkeyring
|
||||
install -d -m0755 "$(dirname "$keyring_path")"
|
||||
tmpkey="$(safe_tempfile)" || tmpkey="/tmp/key.$$"
|
||||
tmpkeyring="$(safe_tempfile)" || tmpkeyring="/tmp/keyring.$$"
|
||||
|
||||
(
|
||||
set -e
|
||||
if exists_cmd curl; then
|
||||
curl -fsSL "$key_url" -o "$tmpkey"
|
||||
elif exists_cmd wget; then
|
||||
wget -qO "$tmpkey" "$key_url"
|
||||
else
|
||||
echo "curl or wget is required to download the key, but none are installed." >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
exists_cmd gpg || {
|
||||
echo "gpg is required to dearmor the key, but is not installed." >&2
|
||||
exit 1
|
||||
}
|
||||
|
||||
gpg --batch --yes --dearmor -o "$tmpkeyring" "$tmpkey"
|
||||
mv -f "$tmpkeyring" "$keyring_path"
|
||||
chown root:root "$keyring_path" || true
|
||||
chmod a+r "$keyring_path"
|
||||
rm -f "$tmpkey"
|
||||
)
|
||||
}
|
||||
|
||||
add_apt_source_file() {
|
||||
local content="$1" dest="$2" tmpfile
|
||||
install -d -m0755 "$(dirname "$dest")"
|
||||
tmpfile="$(safe_tempfile)" || tmpfile="/tmp/src.$$"
|
||||
printf '%s\n' "$content" >"$tmpfile"
|
||||
|
||||
if [[ -f "$dest" ]] && cmp -s "$tmpfile" "$dest"; then
|
||||
rm -f "$tmpfile"
|
||||
return 0
|
||||
fi
|
||||
|
||||
mv -f "$tmpfile" "$dest"
|
||||
chmod 0644 "$dest"
|
||||
return 0
|
||||
}
|
||||
|
||||
add_apt_sources_file() {
|
||||
add_apt_source_file "$1" "$2"
|
||||
}
|
||||
|
||||
install_apt_repo() {
|
||||
local key_url="$1" keyring_path="$2" sources_content="$3" sources_path="$4"
|
||||
shift 4
|
||||
local pkgs=("$@") all_installed=true p key_added=false src_changed=false
|
||||
|
||||
for p in "${pkgs[@]}"; do
|
||||
if ! dpkg-query -W -f='${Status}' "$p" 2>/dev/null | grep -q 'installed'; then
|
||||
all_installed=false
|
||||
break
|
||||
fi
|
||||
done
|
||||
$all_installed && return 0
|
||||
|
||||
if [[ ! -f "$keyring_path" ]]; then
|
||||
if add_apt_key_from_url "$key_url" "$keyring_path"; then
|
||||
key_added=true
|
||||
else
|
||||
log_warn "failed to add key from $key_url"
|
||||
fi
|
||||
fi
|
||||
|
||||
if [[ ! -f "$sources_path" ]] || ! printf '%s\n' "$sources_content" | cmp -s - "$sources_path"; then
|
||||
src_changed=true
|
||||
add_apt_sources_file "$sources_content" "$sources_path"
|
||||
fi
|
||||
|
||||
if $key_added || $src_changed; then
|
||||
apt-get update
|
||||
fi
|
||||
return 0
|
||||
}
|
||||
|
||||
###############################################################################
|
||||
### Systeme
|
||||
#
|
||||
|
||||
ufw_initialize() {
|
||||
ufw --force reset >/dev/null || true
|
||||
ufw default deny incoming
|
||||
ufw default allow outgoing
|
||||
ufw --force enable
|
||||
}
|
||||
|
||||
apply_ufw_rules_file() {
|
||||
local rules_file="$1" rule
|
||||
[[ -f "$rules_file" ]] || return 0
|
||||
|
||||
while IFS= read -r rule || [[ -n "$rule" ]]; do
|
||||
rule="${rule%%#*}"
|
||||
rule="$(echo "$rule" | xargs)"
|
||||
[[ -z "$rule" ]] && continue
|
||||
log_info "Allowing firewall rule: $rule"
|
||||
ufw allow "$rule"
|
||||
done <"$rules_file"
|
||||
return 0
|
||||
}
|
||||
|
||||
remove_primary_network_section() {
|
||||
local if_file="/etc/network/interfaces" header='# The primary network interface' tmp
|
||||
if [[ -f "$if_file" ]] && grep -q "^${header}" "$if_file" && sed --version >/dev/null 2>&1; then
|
||||
tmp="$(mktemp --tmpdir netif.XXXXXX)" || tmp="/tmp/netif.$$"
|
||||
sed "/${header}/Q" "$if_file" >"$tmp" || true
|
||||
mv -f "$tmp" "$if_file"
|
||||
fi
|
||||
}
|
||||
|
||||
ensure_grub_cmdline() {
|
||||
local f="${1:-/etc/default/grub}" from="${2:-quiet}" to="${3:-quiet loglevel=3 nowatchdog}"
|
||||
local pat="^GRUB_CMDLINE_LINUX_DEFAULT=\"${from}\"$"
|
||||
local repl="GRUB_CMDLINE_LINUX_DEFAULT=\"${to}\""
|
||||
|
||||
[[ -r "$f" ]] || {
|
||||
echo "File not found or not readable: $f" >&2
|
||||
return 1
|
||||
}
|
||||
|
||||
if grep -qE "$pat" "$f"; then
|
||||
sed -i "s|${pat}|${repl}|" "$f"
|
||||
command -v update-grub >/dev/null 2>&1 && update-grub || true
|
||||
log_ok "GRUB configuration updated"
|
||||
else
|
||||
log_skip "GRUB command line already compliant"
|
||||
fi
|
||||
return 0
|
||||
}
|
||||
|
||||
service_exists() {
|
||||
local service="$1"
|
||||
systemctl list-unit-files --type=service --no-legend 2>/dev/null | awk '{print $1}' | grep -Fxq "${service}.service"
|
||||
}
|
||||
|
||||
restart_service_if_present() {
|
||||
local service="$1"
|
||||
if service_exists "$service"; then
|
||||
log_info "Restarting ${service}"
|
||||
systemctl restart "$service" || true
|
||||
else
|
||||
log_skip "service ${service} absent"
|
||||
fi
|
||||
}
|
||||
|
||||
###############################################################################
|
||||
### Utilisateur / roles
|
||||
#
|
||||
|
||||
get_target_user() {
|
||||
local user=""
|
||||
|
||||
if [[ -n "${SUDO_USER:-}" && "${SUDO_USER}" != "root" ]]; then
|
||||
printf '%s\n' "$SUDO_USER"
|
||||
return 0
|
||||
fi
|
||||
|
||||
if [[ -n "${PKEXEC_UID:-}" ]] && user="$(id -nu "$PKEXEC_UID" 2>/dev/null || true)" && [[ -n "$user" && "$user" != "root" ]]; then
|
||||
printf '%s\n' "$user"
|
||||
return 0
|
||||
fi
|
||||
|
||||
user="$(logname 2>/dev/null || true)"
|
||||
if [[ -n "$user" && "$user" != "root" ]]; then
|
||||
printf '%s\n' "$user"
|
||||
return 0
|
||||
fi
|
||||
|
||||
return 1
|
||||
}
|
||||
|
||||
install_code_extensions() {
|
||||
local code_bin="$1"
|
||||
shift
|
||||
[[ -n "$code_bin" && "$#" -gt 0 ]] || return 0
|
||||
|
||||
if ! command -v "$code_bin" >/dev/null 2>&1 && [[ -x "/usr/bin/$code_bin" ]]; then
|
||||
code_bin="/usr/bin/$code_bin"
|
||||
elif ! command -v "$code_bin" >/dev/null 2>&1; then
|
||||
echo "Code binary $code_bin not found; skipping extensions install." >&2
|
||||
return 0
|
||||
fi
|
||||
|
||||
local run_as ext installed_list
|
||||
run_as="$(get_target_user 2>/dev/null || true)"
|
||||
|
||||
log_info "Synchronizing ${code_bin##*/} extensions"
|
||||
|
||||
if [[ -n "$run_as" ]]; then
|
||||
installed_list="$(sudo -u "$run_as" env PATH="${PATH:-/usr/bin:/bin}" "$code_bin" --list-extensions 2>/dev/null || true)"
|
||||
else
|
||||
installed_list="$("$code_bin" --list-extensions 2>/dev/null || true)"
|
||||
fi
|
||||
|
||||
for ext in "$@"; do
|
||||
if grep -Fqx "$ext" <<<"$installed_list"; then
|
||||
log_skip "extension '$ext' already installed"
|
||||
continue
|
||||
fi
|
||||
|
||||
log_info "Installing extension '$ext'"
|
||||
if [[ -n "$run_as" ]]; then
|
||||
sudo -u "$run_as" env PATH="${PATH:-/usr/bin:/bin}" "$code_bin" --install-extension "$ext" --force || true
|
||||
else
|
||||
"$code_bin" --install-extension "$ext" --force || true
|
||||
fi
|
||||
done
|
||||
return 0
|
||||
}
|
||||
|
||||
configure_php_no_jit() {
|
||||
local php_version="${1:-$(php -r 'echo PHP_MAJOR_VERSION.".".PHP_MINOR_VERSION;' 2>/dev/null || true)}"
|
||||
local mod_name="no-jit" mods_dir mod_file sapi
|
||||
local config_content='; Disable JIT because it conflicts with Xdebug
|
||||
opcache.jit=0
|
||||
opcache.jit_buffer_size=0
|
||||
'
|
||||
|
||||
[[ -n "$php_version" ]] || {
|
||||
echo "Unable to detect PHP version." >&2
|
||||
return 1
|
||||
}
|
||||
|
||||
log_info "Using PHP version: ${php_version}"
|
||||
[[ -d "/etc/php/${php_version}" ]] || {
|
||||
echo "PHP config directory not found: /etc/php/${php_version}" >&2
|
||||
return 1
|
||||
}
|
||||
|
||||
mods_dir="/etc/php/${php_version}/mods-available"
|
||||
mod_file="${mods_dir}/${mod_name}.ini"
|
||||
install -d "$mods_dir"
|
||||
write_text_file_if_changed "$config_content" "$mod_file" >/dev/null || true
|
||||
|
||||
if exists_cmd phpenmod; then
|
||||
phpenmod -v "$php_version" "$mod_name"
|
||||
else
|
||||
echo "phpenmod not found; skipping module enablement." >&2
|
||||
fi
|
||||
|
||||
if exists_cmd phpquery; then
|
||||
echo
|
||||
echo "Module status by SAPI:"
|
||||
for sapi in cli apache2 fpm; do
|
||||
if phpquery -v "$php_version" -s "$sapi" -m "$mod_name" >/dev/null 2>&1; then
|
||||
echo " - ${sapi}: enabled"
|
||||
elif [[ -d "/etc/php/${php_version}/${sapi}" ]]; then
|
||||
echo " - ${sapi}: not enabled"
|
||||
fi
|
||||
done
|
||||
fi
|
||||
|
||||
restart_service_if_present apache2
|
||||
restart_service_if_present "php${php_version}-fpm"
|
||||
|
||||
echo
|
||||
echo "Verification:"
|
||||
php -i | grep -E '^opcache.jit =>|^opcache.jit_buffer_size =>|^Loaded Configuration File|^Scan this dir for additional .ini files' || true
|
||||
return 0
|
||||
}
|
||||
|
||||
load_role_packages() {
|
||||
local role="$1"
|
||||
local packages_file="$ROLE_DIR/$role/packages.list"
|
||||
ROLE_PACKAGES=()
|
||||
|
||||
if [[ -f "$packages_file" ]]; then
|
||||
# shellcheck disable=SC1090
|
||||
source "$packages_file"
|
||||
fi
|
||||
|
||||
return 0
|
||||
}
|
||||
|
||||
append_localized_packages() {
|
||||
local -n target_ref="$1"
|
||||
local lang_override="${lang:-}" l10n_fragment="$ROLE_DIR/base/l10n.packages"
|
||||
local mapping base prefix cand
|
||||
|
||||
[[ -n "$lang_override" && -f "$l10n_fragment" ]] || return 0
|
||||
|
||||
L10N_MAP_PKGS=()
|
||||
source "$l10n_fragment"
|
||||
apt-get update >/dev/null 2>&1 || true
|
||||
|
||||
for mapping in "${L10N_MAP_PKGS[@]:-}"; do
|
||||
base="${mapping%%::*}"
|
||||
prefix="${mapping##*::}"
|
||||
cand="${prefix}-${lang_override}"
|
||||
|
||||
if apt-cache show "$cand" >/dev/null 2>&1 || {
|
||||
dpkg-query -W -f='${Status}' "$base" 2>/dev/null | grep -q 'installed' &&
|
||||
! dpkg-query -W -f='${Status}' "$cand" 2>/dev/null | grep -q 'installed'
|
||||
}; then
|
||||
case " ${target_ref[*]} " in
|
||||
*" $cand "*) : ;;
|
||||
*) target_ref+=("$cand") ;;
|
||||
esac
|
||||
fi
|
||||
done
|
||||
return 0
|
||||
}
|
||||
|
||||
verify_role_manifest() {
|
||||
local role="$1"
|
||||
local manifest="$ROLE_DIR/$role/packages.list"
|
||||
local line trimmed
|
||||
|
||||
[[ -f "$manifest" ]] || return 0
|
||||
|
||||
while IFS= read -r line || [[ -n "$line" ]]; do
|
||||
trimmed="${line#"${line%%[![:space:]]*}"}"
|
||||
[[ -z "$trimmed" ]] && continue
|
||||
[[ "$trimmed" =~ ^# ]] && continue
|
||||
if [[ "$trimmed" =~ [[:space:]] ]]; then
|
||||
fatal "Invalid package entry in $manifest: $trimmed"
|
||||
fi
|
||||
done <"$manifest"
|
||||
return 0
|
||||
}
|
||||
|
||||
validate_role() {
|
||||
local role="$1"
|
||||
local role_path="$ROLE_DIR/$role"
|
||||
local allowed='^(repo\.sh|install\.sh|config\.sh|run\.sh|packages\.list|l10n\.packages|rules\.[A-Za-z0-9_-]+\.list)$'
|
||||
local item base
|
||||
|
||||
require_valid_name "role" "$role"
|
||||
[[ -d "$role_path" ]] || fatal "Role not found: $role"
|
||||
|
||||
shopt -s nullglob
|
||||
for item in "$role_path"/*; do
|
||||
base="$(basename "$item")"
|
||||
[[ "$base" =~ $allowed ]] || fatal "Unexpected file in role $role: $base"
|
||||
done
|
||||
shopt -u nullglob
|
||||
return 0
|
||||
}
|
||||
|
||||
run_role_script() {
|
||||
local role="$1"
|
||||
local step="$2"
|
||||
local script="$ROLE_DIR/$role/$step"
|
||||
local step_name="$role/$step"
|
||||
|
||||
if [[ ! -f "$script" ]]; then
|
||||
return 0
|
||||
fi
|
||||
|
||||
log_run "$step_name"
|
||||
if dry_run_enabled; then
|
||||
log_ok "$step_name (dry-run)"
|
||||
record_skipped_step "$step_name (dry-run)"
|
||||
return 0
|
||||
fi
|
||||
|
||||
if bash "$script"; then
|
||||
log_ok "$step_name"
|
||||
record_executed_step "$step_name"
|
||||
else
|
||||
record_failed_step "$step_name"
|
||||
fatal "Step failed: $step_name"
|
||||
fi
|
||||
return 0
|
||||
}
|
||||
|
||||
run_role_packages() {
|
||||
local role="$1"
|
||||
local -n pkgs_ref="$2"
|
||||
local step_name="$role/packages.list"
|
||||
|
||||
if ((${#pkgs_ref[@]} == 0)); then
|
||||
log_skip "$step_name empty"
|
||||
record_skipped_step "$step_name"
|
||||
return 0
|
||||
fi
|
||||
|
||||
log_run "$step_name"
|
||||
if dry_run_enabled; then
|
||||
printf '[DRY] Would ensure packages: %s\n' "${pkgs_ref[*]}"
|
||||
log_ok "$step_name (dry-run)"
|
||||
record_skipped_step "$step_name (dry-run)"
|
||||
return 0
|
||||
fi
|
||||
|
||||
local rc=0
|
||||
if ensure_packages_installed "${pkgs_ref[@]}"; then
|
||||
rc=0
|
||||
else
|
||||
rc=$?
|
||||
fi
|
||||
|
||||
case "$rc" in
|
||||
0)
|
||||
log_ok "$step_name"
|
||||
record_executed_step "$step_name"
|
||||
;;
|
||||
3)
|
||||
log_ok "$step_name (already compliant)"
|
||||
record_skipped_step "$step_name"
|
||||
;;
|
||||
*)
|
||||
return 1
|
||||
;;
|
||||
esac
|
||||
return 0
|
||||
}
|
||||
|
||||
run_role() {
|
||||
local role="$1"
|
||||
local -a role_packages=()
|
||||
|
||||
validate_role "$role"
|
||||
echo
|
||||
echo "==== Role: $role ===="
|
||||
|
||||
run_role_script "$role" repo.sh || return 1
|
||||
load_role_packages "$role" || return 1
|
||||
role_packages=("${ROLE_PACKAGES[@]:-}")
|
||||
append_localized_packages role_packages || return 1
|
||||
run_role_packages "$role" role_packages || return 1
|
||||
run_role_script "$role" install.sh || return 1
|
||||
run_role_script "$role" config.sh || return 1
|
||||
run_role_script "$role" run.sh || return 1
|
||||
|
||||
record_executed_role "$role"
|
||||
return 0
|
||||
}
|
||||
|
||||
list_available_profiles() {
|
||||
local p
|
||||
shopt -s nullglob
|
||||
for p in "$PROFILE_DIR"/*.sh; do
|
||||
basename "${p%.sh}"
|
||||
done
|
||||
shopt -u nullglob
|
||||
return 0
|
||||
}
|
||||
|
||||
list_available_roles() {
|
||||
local r
|
||||
shopt -s nullglob
|
||||
for r in "$ROLE_DIR"/*; do
|
||||
[[ -d "$r" ]] || continue
|
||||
basename "$r"
|
||||
done
|
||||
shopt -u nullglob
|
||||
return 0
|
||||
}
|
||||
|
||||
validate_profile_definition() {
|
||||
local profile_file="$1"
|
||||
[[ -f "$profile_file" ]] || fatal "Profile definition not found: $profile_file"
|
||||
|
||||
declare -a ROLE_ORDER=()
|
||||
# shellcheck disable=SC1090
|
||||
source "$profile_file"
|
||||
((${#ROLE_ORDER[@]} > 0)) || fatal "No roles declared in $profile_file"
|
||||
return 0
|
||||
}
|
||||
|
||||
validate_all_roles() {
|
||||
local role
|
||||
for role in "$@"; do
|
||||
validate_role "$role"
|
||||
verify_role_manifest "$role"
|
||||
done
|
||||
return 0
|
||||
}
|
||||
Reference in New Issue
Block a user