mirror of
https://github.com/rcourtman/Pulse.git
synced 2026-02-18 23:41:48 +01:00
Implements a comprehensive script improvement infrastructure to reduce code duplication, improve maintainability, and enable easier testing of installer scripts. ## New Infrastructure ### Shared Library System (scripts/lib/) - common.sh: Core utilities (logging, sudo, dry-run, cleanup management) - systemd.sh: Service management helpers with container-safe systemctl - http.sh: HTTP/download helpers with curl/wget fallback and retry logic - README.md: Complete API documentation for all library functions ### Bundler System - scripts/bundle.sh: Concatenates library modules into single-file installers - scripts/bundle.manifest: Defines bundling configuration for distributables - Enables both modular development and curl|bash distribution ### Test Infrastructure - scripts/tests/run.sh: Test harness for running all smoke tests - scripts/tests/test-common-lib.sh: Common library validation (5 tests) - scripts/tests/test-docker-agent-v2.sh: Installer smoke tests (4 tests) - scripts/tests/integration/: Container-based integration tests (5 scenarios) - All tests passing ✓ ## Refactored Installer ### install-docker-agent-v2.sh - Reduced from 1098 to 563 lines (48% code reduction) - Uses shared libraries for all common operations - NEW: --dry-run flag support - Maintains 100% backward compatibility with original - Fully tested with smoke and integration tests ### Key Improvements - Sudo escalation: 100+ lines → 1 function call - Download logic: 51 lines → 1 function call - Service creation: 33 lines → 2 function calls - Logging: Standardized across all operations - Error handling: Improved with common library ## Documentation ### Rollout Strategy (docs/installer-v2-rollout.md) - 3-phase rollout plan (Alpha → Beta → GA) - Feature flag mechanism for gradual deployment - Testing checklist and success metrics - Rollback procedures and communication plan ### Developer Guides - docs/script-library-guide.md: Complete library usage guide - docs/CONTRIBUTING-SCRIPTS.md: Contribution workflow - docs/installer-v2-quickref.md: Quick reference for operators ## Metrics - Code reduction: 48% (1098 → 563 lines) - Reusable functions: 0 → 30+ - Test coverage: 0 → 8 test scenarios - Documentation: 0 → 5 comprehensive guides ## Testing All tests passing: - Smoke tests: 2/2 passed (8 test cases) - Integration tests: 5/5 scenarios passed - Bundled output: Syntax validated, dry-run tested ## Next Steps This lays the foundation for migrating other installers (install.sh, install-sensor-proxy.sh) to use the same pattern, reducing overall maintenance burden and improving code quality across the project.
189 lines
4.7 KiB
Bash
189 lines
4.7 KiB
Bash
#!/usr/bin/env bash
|
|
#
|
|
# Systemd management helpers for Pulse shell scripts.
|
|
|
|
set -euo pipefail
|
|
|
|
# Ensure the common library is available when sourced directly.
|
|
if ! declare -F common::log_info >/dev/null 2>&1; then
|
|
# shellcheck disable=SC1091
|
|
source "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/common.sh"
|
|
fi
|
|
|
|
declare -g SYSTEMD_TIMEOUT_SEC="${SYSTEMD_TIMEOUT_SEC:-20}"
|
|
|
|
# systemd::safe_systemctl args...
|
|
# Executes systemctl with sensible defaults and optional timeout guards.
|
|
systemd::safe_systemctl() {
|
|
if ! command -v systemctl >/dev/null 2>&1; then
|
|
common::fail "systemctl not available on this host"
|
|
fi
|
|
|
|
local -a cmd
|
|
systemd::__build_cmd cmd "$@"
|
|
systemd::__wrap_timeout cmd
|
|
|
|
local label="systemctl $*"
|
|
common::run --label "${label}" -- "${cmd[@]}"
|
|
}
|
|
|
|
# systemd::detect_service_name [service...]
|
|
# Returns the first existing service name from the provided list.
|
|
systemd::detect_service_name() {
|
|
local -a candidates=("$@")
|
|
if ((${#candidates[@]} == 0)); then
|
|
candidates=(pulse.service pulse-backend.service pulse-docker-agent.service pulse-hot-dev.service)
|
|
fi
|
|
|
|
local name
|
|
for name in "${candidates[@]}"; do
|
|
if systemd::service_exists "${name}"; then
|
|
printf '%s\n' "$(systemd::__normalize_unit "${name}")"
|
|
return 0
|
|
fi
|
|
done
|
|
return 1
|
|
}
|
|
|
|
# systemd::service_exists service
|
|
# Returns success if the given service unit exists.
|
|
systemd::service_exists() {
|
|
local unit
|
|
unit="$(systemd::__normalize_unit "${1:-}")" || return 1
|
|
|
|
if command -v systemctl >/dev/null 2>&1 && ! common::is_dry_run; then
|
|
local -a cmd
|
|
systemd::__build_cmd cmd "list-unit-files" "${unit}"
|
|
systemd::__wrap_timeout cmd
|
|
local output=""
|
|
if output="$("${cmd[@]}" 2>/dev/null)"; then
|
|
[[ "${output}" =~ ^${unit}[[:space:]] ]] && return 0
|
|
fi
|
|
fi
|
|
|
|
local paths=(
|
|
"/etc/systemd/system/${unit}"
|
|
"/lib/systemd/system/${unit}"
|
|
"/usr/lib/systemd/system/${unit}"
|
|
)
|
|
local path
|
|
for path in "${paths[@]}"; do
|
|
if [[ -f "${path}" ]]; then
|
|
return 0
|
|
fi
|
|
done
|
|
return 1
|
|
}
|
|
|
|
# systemd::is_active service
|
|
# Checks whether the given service is active.
|
|
systemd::is_active() {
|
|
local unit
|
|
unit="$(systemd::__normalize_unit "${1:-}")" || return 1
|
|
|
|
if common::is_dry_run; then
|
|
return 1
|
|
fi
|
|
|
|
if ! command -v systemctl >/dev/null 2>&1; then
|
|
return 1
|
|
fi
|
|
|
|
local -a cmd
|
|
systemd::__build_cmd cmd "is-active" "--quiet" "${unit}"
|
|
systemd::__wrap_timeout cmd
|
|
if "${cmd[@]}" >/dev/null 2>&1; then
|
|
return 0
|
|
fi
|
|
return 1
|
|
}
|
|
|
|
# systemd::create_service /path/to/unit.service
|
|
# Reads unit file content from stdin and writes it to the supplied path.
|
|
systemd::create_service() {
|
|
local target="${1:-}"
|
|
local mode="${2:-0644}"
|
|
if [[ -z "${target}" ]]; then
|
|
common::fail "systemd::create_service requires a target path"
|
|
fi
|
|
|
|
local content
|
|
content="$(cat)"
|
|
|
|
if common::is_dry_run; then
|
|
common::log_info "[dry-run] Would write systemd unit ${target}"
|
|
common::log_debug "${content}"
|
|
return 0
|
|
fi
|
|
|
|
mkdir -p "$(dirname "${target}")"
|
|
printf '%s' "${content}" > "${target}"
|
|
chmod "${mode}" "${target}"
|
|
common::log_info "Wrote unit file: ${target}"
|
|
}
|
|
|
|
# systemd::enable_and_start service
|
|
# Reloads systemd, enables, and starts the given service.
|
|
systemd::enable_and_start() {
|
|
local unit
|
|
unit="$(systemd::__normalize_unit "${1:-}")" || common::fail "Invalid systemd unit name"
|
|
|
|
systemd::safe_systemctl daemon-reload
|
|
systemd::safe_systemctl enable "${unit}"
|
|
systemd::safe_systemctl start "${unit}"
|
|
}
|
|
|
|
# systemd::restart service
|
|
# Safely restarts the given service.
|
|
systemd::restart() {
|
|
local unit
|
|
unit="$(systemd::__normalize_unit "${1:-}")" || common::fail "Invalid systemd unit name"
|
|
systemd::safe_systemctl restart "${unit}"
|
|
}
|
|
|
|
# Internal: build systemctl command array.
|
|
systemd::__build_cmd() {
|
|
local -n ref=$1
|
|
shift
|
|
ref=("systemctl" "--no-ask-password" "--no-pager")
|
|
ref+=("$@")
|
|
}
|
|
|
|
# Internal: wrap command with timeout if necessary.
|
|
systemd::__wrap_timeout() {
|
|
local -n ref=$1
|
|
if ! command -v timeout >/dev/null 2>&1; then
|
|
return
|
|
fi
|
|
if systemd::__should_timeout; then
|
|
local -a wrapped=("timeout" "${SYSTEMD_TIMEOUT_SEC}s")
|
|
wrapped+=("${ref[@]}")
|
|
ref=("${wrapped[@]}")
|
|
fi
|
|
}
|
|
|
|
# Internal: determine if we are in a container environment.
|
|
systemd::__should_timeout() {
|
|
if [[ -f /run/systemd/container ]]; then
|
|
return 0
|
|
fi
|
|
if command -v systemd-detect-virt >/dev/null 2>&1; then
|
|
if systemd-detect-virt --quiet --container; then
|
|
return 0
|
|
fi
|
|
fi
|
|
return 1
|
|
}
|
|
|
|
# Internal: normalize service names to include .service suffix.
|
|
systemd::__normalize_unit() {
|
|
local unit="${1:-}"
|
|
if [[ -z "${unit}" ]]; then
|
|
return 1
|
|
fi
|
|
if [[ "${unit}" != *.service ]]; then
|
|
unit="${unit}.service"
|
|
fi
|
|
printf '%s\n' "${unit}"
|
|
}
|