136 lines
2.9 KiB
Bash
Executable File
136 lines
2.9 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
set -euo pipefail
|
|
|
|
if [[ -t 2 ]]; then
|
|
_C_RESET='\033[0m'
|
|
_C_RED='\033[0;31m'
|
|
_C_GREEN='\033[0;32m'
|
|
_C_YELLOW='\033[0;33m'
|
|
_C_BLUE='\033[0;34m'
|
|
else
|
|
_C_RESET='' _C_RED='' _C_GREEN='' _C_YELLOW='' _C_BLUE=''
|
|
fi
|
|
|
|
log_info() {
|
|
printf '%b[INFO]%b %s\n' "$_C_BLUE" "$_C_RESET" "$*" >&2
|
|
}
|
|
|
|
log_warn() {
|
|
printf '%b[WARN]%b %s\n' "$_C_YELLOW" "$_C_RESET" "$*" >&2
|
|
}
|
|
|
|
log_error() {
|
|
printf '%b[ERROR]%b %s\n' "$_C_RED" "$_C_RESET" "$*" >&2
|
|
}
|
|
|
|
log_success() {
|
|
printf '%b[OK]%b %s\n' "$_C_GREEN" "$_C_RESET" "$*" >&2
|
|
}
|
|
|
|
require_cmd() {
|
|
local cmd
|
|
for cmd in "$@"; do
|
|
if ! command -v "$cmd" >/dev/null 2>&1; then
|
|
log_error "Required command not found: $cmd"
|
|
return 1
|
|
fi
|
|
done
|
|
}
|
|
|
|
confirm_action() {
|
|
local prompt="${1:-Continue?}"
|
|
local auto_yes="${2:-false}"
|
|
|
|
if [[ "$auto_yes" == "true" ]]; then
|
|
return 0
|
|
fi
|
|
|
|
printf '%s [y/N] ' "$prompt"
|
|
read -r reply
|
|
[[ "$reply" =~ ^[Yy]$ ]]
|
|
}
|
|
|
|
stack_script_dir() {
|
|
cd "$(dirname "${BASH_SOURCE[1]:-${BASH_SOURCE[0]}}")" && pwd
|
|
}
|
|
|
|
load_stack_env() {
|
|
local env_file="$1"
|
|
|
|
if [[ ! -f "$env_file" ]]; then
|
|
log_error "Missing env file: $env_file"
|
|
log_info "Copy stack.env.example to stack.env and update secrets first."
|
|
return 1
|
|
fi
|
|
|
|
local line key value
|
|
while IFS= read -r line || [[ -n "$line" ]]; do
|
|
[[ -z "$line" || "$line" == \#* ]] && continue
|
|
[[ "$line" == *=* ]] || continue
|
|
|
|
key="${line%%=*}"
|
|
value="${line#*=}"
|
|
value="${value%%# *}"
|
|
if [[ "$value" =~ ^\"(.*)\"$ ]] || [[ "$value" =~ ^\'(.*)\'$ ]]; then
|
|
value="${BASH_REMATCH[1]}"
|
|
fi
|
|
value="${value%"${value##*[![:space:]]}"}"
|
|
|
|
export "$key=$value"
|
|
done < "$env_file"
|
|
|
|
: "${OPS_ROOT:=/srv/ops}"
|
|
: "${COMPOSE_PROJECT_NAME:=pi-monitoring}"
|
|
}
|
|
|
|
compose_file() {
|
|
local dir
|
|
dir="$(stack_script_dir)"
|
|
printf '%s/docker-compose.yml' "$dir"
|
|
}
|
|
|
|
ensure_ops_dirs() {
|
|
local root="$1"
|
|
sudo mkdir -p \
|
|
"$root/portainer/data" \
|
|
"$root/grafana/data" \
|
|
"$root/prometheus/data" \
|
|
"$root/prometheus/targets" \
|
|
"$root/uptime-kuma/data" \
|
|
"$root/backups"
|
|
}
|
|
|
|
prepare_permissions() {
|
|
local root="$1"
|
|
|
|
# Keep operational directories writable by the current admin user.
|
|
sudo chown -R "$USER:$USER" \
|
|
"$root/portainer/data" \
|
|
"$root/uptime-kuma/data" \
|
|
"$root/prometheus/targets" \
|
|
"$root/backups"
|
|
|
|
# Grafana UID/GID in official image is usually 472
|
|
sudo chown -R 472:472 "$root/grafana/data"
|
|
# Prometheus runs as nobody (65534) in official image
|
|
sudo chown -R 65534:65534 "$root/prometheus/data"
|
|
}
|
|
|
|
wait_for_container_running() {
|
|
local container_id="$1"
|
|
local timeout_sec="$2"
|
|
local elapsed=0
|
|
|
|
while (( elapsed < timeout_sec )); do
|
|
local state
|
|
state="$(docker inspect -f '{{.State.Status}}' "$container_id" 2>/dev/null || true)"
|
|
if [[ "$state" == "running" ]]; then
|
|
return 0
|
|
fi
|
|
sleep 2
|
|
elapsed=$((elapsed + 2))
|
|
done
|
|
|
|
return 1
|
|
}
|