Files
Pulse/scripts/bundle.sh
rcourtman 0fcfad3dc5 feat: add shared script library system and refactor docker-agent installer
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.
2025-10-20 15:13:38 +00:00

113 lines
2.7 KiB
Bash
Executable File

#!/usr/bin/env bash
#
# Bundle modular installer scripts into distributable single files.
# Reads scripts/bundle.manifest for bundling instructions.
set -euo pipefail
ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
MANIFEST_PATH="${ROOT_DIR}/scripts/bundle.manifest"
HEADER_FMT='# === Begin: %s ==='
FOOTER_FMT='# === End: %s ==='
main() {
[[ -f "${MANIFEST_PATH}" ]] || {
echo "Missing manifest: ${MANIFEST_PATH}" >&2
exit 1
}
local current_output=""
local -a buffer=()
local found_output=false
while IFS= read -r line || [[ -n "${line}" ]]; do
# Skip blank lines and comments.
[[ -z "${line}" || "${line}" =~ ^[[:space:]]*# ]] && continue
if [[ "${line}" =~ ^output:[[:space:]]+(.+) ]]; then
flush_buffer "${current_output}" buffer
buffer=()
current_output="$(normalize_path "${BASH_REMATCH[1]}")"
mkdir -p "$(dirname "${current_output}")"
found_output=true
continue
fi
[[ -n "${current_output}" ]] || {
echo "Encountered source before output directive: ${line}" >&2
exit 1
}
local source_path
source_path="$(normalize_path "${line}")"
if [[ ! -f "${source_path}" ]]; then
echo "Source file missing: ${source_path}" >&2
exit 1
fi
buffer+=("$(printf "${HEADER_FMT}" "$(relative_path "${source_path}")")")
buffer+=("$(<"${source_path}")")
buffer+=("$(printf "${FOOTER_FMT}" "$(relative_path "${source_path}")")"$'\n')
done < "${MANIFEST_PATH}"
flush_buffer "${current_output}" buffer
if [[ "${found_output}" == false ]]; then
echo "No output directive found in ${MANIFEST_PATH}" >&2
exit 1
fi
echo "Bundling complete."
}
# flush_buffer <output_path> <buffer_array>
flush_buffer() {
local target="$1"
shift
[[ $# -gt 0 ]] || return 0
local -n buf_ref=$1
if [[ -z "${target}" ]]; then
return 0
fi
{
echo "# Generated file. Do not edit."
printf '# Bundled on: %s\n' "$(date -u +"%Y-%m-%dT%H:%M:%SZ")"
printf '# Manifest: %s\n\n' "$(relative_path "${MANIFEST_PATH}")"
printf '%s\n' "${buf_ref[@]}"
} > "${target}.tmp"
mv "${target}.tmp" "${target}"
if [[ "${target}" == *.sh ]]; then
chmod +x "${target}"
fi
echo "Wrote $(relative_path "${target}")"
}
# normalize_path <relative_or_absolute_path>
normalize_path() {
local path="$1"
if [[ "${path}" == /* ]]; then
printf '%s\n' "${path}"
else
printf '%s\n' "${ROOT_DIR}/${path}"
fi
}
# relative_path <absolute_path>
relative_path() {
local path="$1"
python3 - "$ROOT_DIR" "$path" <<'PY'
import os
import sys
root = os.path.abspath(sys.argv[1])
target = os.path.abspath(sys.argv[2])
print(os.path.relpath(target, root))
PY
}
main "$@"