#!/bin/bash
set -euo pipefail
INSTALL_DIR="${1:?Error: INSTALL_DIR is required}"
SSL_ENABLED="${2:-true}"
SSL_CERT_PATH="${3:-}"
SSL_KEY_PATH="${4:-}"
SSL_CA_CERT_PATH="${5:-}"
GA_USER="virtcca-ga"
GA_GROUP="virtcca-ga"
NETWORK_SCRIPTS_DIR="/etc/sysconfig/network-scripts"
MONITOR_LOG_DIR="/var/log/virtcca/monitor"
MONITOR_CACHE_DIR="/tmp/monitor_cache"
SSL_CERT_DIR="/etc/virtcca_deploy/cert"
IFCFG_TEMPLATE_DIR="/etc/virtcca_deploy"
SCRIPT_PID=$$
START_TIME=$(date +%s)
START_TIME_HUMAN=$(date '+%Y-%m-%d %H:%M:%S')
ERROR_COUNT=0
WARN_COUNT=0
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
CYAN='\033[0;36m'
NC='\033[0m'
log() {
local level="$1"
local module="$2"
shift 2
local msg="$*"
local ts
ts=$(date '+%Y-%m-%d %H:%M:%S')
local log_line="[${ts}] [${level}] [${module}] [PID:${SCRIPT_PID}] ${msg}"
case "$level" in
INFO) echo -e "${GREEN}${log_line}${NC}" ;;
WARN) echo -e "${YELLOW}${log_line}${NC}"; ((WARN_COUNT++)) || true ;;
ERROR) echo -e "${RED}${log_line}${NC}"; ((ERROR_COUNT++)) || true ;;
DEBUG) echo -e "${CYAN}${log_line}${NC}" ;;
STEP) echo -e "${BLUE}--- ${log_line} ---${NC}" ;;
esac
}
log_info() { log "INFO" "$1" "$2"; }
log_warn() { log "WARN" "$1" "$2"; }
log_error() { log "ERROR" "$1" "$2"; }
log_debug() { log "DEBUG" "$1" "$2"; }
log_step() { log "STEP" "$1" "$2"; }
die() {
local msg="${1:-Unknown error}"
local module="${2:-SETUP}"
log_error "$module" "$msg"
log_error "$module" "Setup failed after $(( $(date +%s) - START_TIME ))s"
exit 1
}
step1_init() {
log_step "INIT" "Step 1/9: Initializing"
log_info "INIT" "=========================================="
log_info "INIT" "Guest Agent In-VM Setup Starting"
log_info "INIT" "=========================================="
log_info "INIT" "Start time: $START_TIME_HUMAN"
log_info "INIT" "Process ID: $SCRIPT_PID"
log_info "INIT" "Parameters:"
log_info "INIT" " INSTALL_DIR=$INSTALL_DIR"
log_info "INIT" " SSL_ENABLED=$SSL_ENABLED"
log_info "INIT" " SSL_CERT_PATH=${SSL_CERT_PATH:-<not set>}"
log_info "INIT" " SSL_KEY_PATH=${SSL_KEY_PATH:-<not set>}"
log_info "INIT" " SSL_CA_CERT_PATH=${SSL_CA_CERT_PATH:-<not set>}"
log_info "INIT" " GA_USER=$GA_USER"
log_info "INIT" " GA_GROUP=$GA_GROUP"
}
step2_install_dependencies() {
log_step "DEPS" "Step 2/9: Installing system dependencies"
local required_packages=("python3" "python3-devel" "python3-pip" "hostname" "sudo")
log_info "DEPS" "Installing packages: ${required_packages[*]}"
if yum install -y "${required_packages[@]}" 2>&1; then
log_info "DEPS" "All dependencies installed successfully"
else
die "DEPS" "Failed to install packages: ${required_packages[*]}"
fi
}
step3_create_directories() {
log_step "DIRS" "Step 3/9: Creating directory structure"
mkdir -p "$INSTALL_DIR"
mkdir -p "$INSTALL_DIR/logs"
mkdir -p "$INSTALL_DIR/virtcca_deploy"
mkdir -p "$MONITOR_LOG_DIR"
mkdir -p "$MONITOR_CACHE_DIR"
mkdir -p "$SSL_CERT_DIR"
mkdir -p "$IFCFG_TEMPLATE_DIR"
log_info "DIRS" " [OK] $INSTALL_DIR"
log_info "DIRS" " [OK] $INSTALL_DIR/logs"
log_info "DIRS" " [OK] $INSTALL_DIR/virtcca_deploy"
log_info "DIRS" " [OK] $MONITOR_LOG_DIR"
log_info "DIRS" " [OK] $MONITOR_CACHE_DIR"
log_info "DIRS" " [OK] $SSL_CERT_DIR"
log_info "DIRS" " [OK] $IFCFG_TEMPLATE_DIR"
}
step4_copy_files() {
log_step "COPY" "Step 4/9: Copying virtcca_deploy package"
local source_dir="/tmp/ga_inject/virtcca_deploy/"
if [ ! -d "$source_dir" ]; then
die "COPY" "Source directory not found: $source_dir"
fi
cp -r "$source_dir"/* "$INSTALL_DIR/virtcca_deploy/"
local file_count
file_count=$(find "$INSTALL_DIR/virtcca_deploy" -type f | wc -l)
log_info "COPY" "Copied $file_count files to $INSTALL_DIR/virtcca_deploy"
log_debug "COPY" "Installed components:"
for comp in guest_agent monitor common; do
if [ -d "$INSTALL_DIR/virtcca_deploy/$comp" ]; then
log_debug "COPY" " [OK] $comp/"
else
log_warn "COPY" " [MISSING] $comp/"
fi
done
}
step5_setup_python() {
log_step "PYTHON" "Step 5/9: Setting up Python environment"
local ga_dir="$INSTALL_DIR/virtcca_deploy/guest_agent"
if [ ! -d "$ga_dir" ]; then
die "PYTHON" "guest_agent directory not found in $INSTALL_DIR/virtcca_deploy"
fi
cd "$ga_dir"
mkdir -p "$ga_dir/logs"
if ! command -v python3 &>/dev/null; then
die "PYTHON" "python3 not found after dependency installation"
fi
local python_version
python_version=$(python3 --version 2>&1)
log_info "PYTHON" "Python version: $python_version"
if [ -d "venv" ]; then
log_warn "PYTHON" "Virtual environment already exists, removing..."
rm -rf venv
fi
log_info "PYTHON" "Creating virtual environment..."
if python3 -m venv venv; then
log_info "PYTHON" "Virtual environment created"
else
die "PYTHON" "Failed to create virtual environment"
fi
if [ ! -f "venv/bin/python" ]; then
die "PYTHON" "Virtual environment python not found"
fi
local venv_python_version
venv_python_version=$(venv/bin/python --version 2>&1)
log_info "PYTHON" "Venv python: $venv_python_version"
if [ ! -f "requirements.txt" ]; then
die "PYTHON" "requirements.txt not found in $ga_dir"
fi
log_info "PYTHON" "Installing Python dependencies..."
source venv/bin/activate
if pip install -r requirements.txt \
--index-url https://mirrors.aliyun.com/pypi/simple/ \
--trusted-host mirrors.aliyun.com \
2>&1; then
log_info "PYTHON" "Dependencies installed successfully"
else
die "PYTHON" "Failed to install Python dependencies"
fi
local required_pip_packages=("Flask" "gunicorn" "psutil" "python-dotenv")
for pkg in "${required_pip_packages[@]}"; do
if pip show "$pkg" &>/dev/null; then
local ver
ver=$(pip show "$pkg" 2>/dev/null | grep Version | awk '{print $2}')
log_info "PYTHON" " [OK] $pkg==$ver"
else
log_warn "PYTHON" " [MISSING] $pkg not found"
fi
done
}
step6_generate_config() {
log_step "CONFIG" "Step 6/9: Generating configuration file"
local ga_dir="$INSTALL_DIR/virtcca_deploy/guest_agent"
echo "# Guest Agent Configuration" > "$ga_dir/.env"
echo "# Generated by setup_vm.sh at $START_TIME_HUMAN" >> "$ga_dir/.env"
echo "HOST=0.0.0.0" >> "$ga_dir/.env"
echo "PORT=5003" >> "$ga_dir/.env"
echo "FLASK_DEBUG=false" >> "$ga_dir/.env"
echo "MONITOR_INTERFACE=eth0" >> "$ga_dir/.env"
echo "SSL_ENABLED=$SSL_ENABLED" >> "$ga_dir/.env"
if [ "$SSL_ENABLED" = "true" ] && [ -n "$SSL_CERT_PATH" ] && [ -n "$SSL_KEY_PATH" ]; then
echo "SSL_CERT_FILE=$SSL_CERT_PATH" >> "$ga_dir/.env"
echo "SSL_KEY_FILE=$SSL_KEY_PATH" >> "$ga_dir/.env"
if [ -n "$SSL_CA_CERT_PATH" ]; then
echo "SSL_CA_CERT_FILE=$SSL_CA_CERT_PATH" >> "$ga_dir/.env"
fi
log_info "CONFIG" "SSL configuration written"
else
log_info "CONFIG" "SSL disabled, basic configuration written"
fi
log_info "CONFIG" ".env contents:"
while IFS= read -r line; do
log_info "CONFIG" " $line"
done < "$ga_dir/.env"
if [ ! -f "$ga_dir/.env" ]; then
die "CONFIG" "Failed to create .env file"
fi
}
step7_install_service() {
log_step "SERVICE" "Step 7/9: Installing systemd service"
local service_file="$INSTALL_DIR/virtcca_deploy/guest_agent/virtcca-guest-agent.service"
if [ ! -f "$service_file" ]; then
log_warn "SERVICE" "Service file not found: $service_file"
log_warn "SERVICE" "Skipping service installation"
return 0
fi
cp "$service_file" /etc/systemd/system/
if command -v systemctl &>/dev/null; then
systemctl daemon-reload
if systemctl enable virtcca-guest-agent.service 2>/dev/null; then
log_info "SERVICE" "Service installed and enabled"
else
log_warn "SERVICE" "Service file copied but enable failed (may be in chroot)"
fi
else
log_warn "SERVICE" "systemctl not available, service file copied but not enabled"
fi
if [ -f "/etc/systemd/system/virtcca-guest-agent.service" ]; then
log_info "SERVICE" "Service file verified at /etc/systemd/system/"
else
log_warn "SERVICE" "Service file not found after copy"
fi
}
step8_create_user_and_sudoers() {
log_step "SUDOERS" "Step 8/9: Creating service user and sudoers whitelist"
if id "$GA_USER" &>/dev/null; then
log_info "SUDOERS" "User $GA_USER already exists, skipping creation"
else
useradd -r -s /sbin/nologin -d "$INSTALL_DIR" -c "VirtCCA Guest Agent" "$GA_USER"
log_info "SUDOERS" " [OK] User $GA_USER created (system account, no login shell)"
fi
local sudoers_file="/etc/sudoers.d/virtcca-guest-agent"
cat > "$sudoers_file" <<EOF
# VirtCCA Guest Agent - 最小化 sudo 权限白名单
# 仅允许 guest_agent 执行网络配置相关操作
# 网络配置文件写入: 部署 ifcfg-* 文件
$GA_USER ALL=(root) NOPASSWD: /usr/bin/tee /etc/sysconfig/network-scripts/ifcfg-*
# 网络接口启停: ifdown/ifup 单个接口
$GA_USER ALL=(root) NOPASSWD: /usr/sbin/ifdown *
$GA_USER ALL=(root) NOPASSWD: /usr/sbin/ifup *
# 网络接口启停: ifdown/ifup --all
$GA_USER ALL=(root) NOPASSWD: /usr/sbin/ifdown --all
$GA_USER ALL=(root) NOPASSWD: /usr/sbin/ifup --all
# 网络服务重启: systemctl restart NetworkManager/network
$GA_USER ALL=(root) NOPASSWD: /usr/bin/systemctl restart NetworkManager
$GA_USER ALL=(root) NOPASSWD: /usr/bin/systemctl restart network
# 网络连接关闭: nmcli
$GA_USER ALL=(root) NOPASSWD: /usr/bin/nmcli network off
EOF
chmod 440 "$sudoers_file"
if visudo -c -f "$sudoers_file" &>/dev/null; then
log_info "SUDOERS" " [OK] sudoers file validated: $sudoers_file"
else
log_error "SUDOERS" "sudoers file validation failed, removing"
rm -f "$sudoers_file"
die "SUDOERS" "Failed to create valid sudoers configuration"
fi
log_info "SUDOERS" "Sudoers whitelist configured for $GA_USER"
log_info "SUDOERS" "Allowed commands:"
log_info "SUDOERS" " - tee /etc/sysconfig/network-scripts/ifcfg-*"
log_info "SUDOERS" " - ifdown/ifup <interface>"
log_info "SUDOERS" " - ifdown/ifup --all"
log_info "SUDOERS" " - systemctl restart NetworkManager"
log_info "SUDOERS" " - systemctl restart network"
log_info "SUDOERS" " - nmcli network off"
}
step9_set_permissions() {
log_step "PERM" "Step 9/9: Setting file permissions and ownership"
local ga_dir="$INSTALL_DIR/virtcca_deploy/guest_agent"
chown -R "$GA_USER:$GA_GROUP" "$INSTALL_DIR"
log_info "PERM" " [OK] $INSTALL_DIR ownership -> $GA_USER:$GA_GROUP"
chown "$GA_USER:$GA_GROUP" "$ga_dir/logs"
chmod 755 "$ga_dir/logs"
log_info "PERM" " [OK] $ga_dir/logs ownership -> $GA_USER:$GA_GROUP, mode 755"
if [ -f "$ga_dir/.env" ]; then
chown "$GA_USER:$GA_GROUP" "$ga_dir/.env"
chmod 600 "$ga_dir/.env"
log_info "PERM" " [OK] .env ownership -> $GA_USER:$GA_GROUP, mode 600"
fi
if [ "$SSL_ENABLED" = "true" ]; then
chown "root:$GA_GROUP" "$SSL_CERT_DIR"
chmod 750 "$SSL_CERT_DIR"
log_info "PERM" " [OK] $SSL_CERT_DIR ownership -> root:$GA_GROUP, mode 750"
if [ -n "$SSL_CERT_PATH" ] && [ -f "$SSL_CERT_PATH" ]; then
chown "root:$GA_GROUP" "$SSL_CERT_PATH"
chmod 640 "$SSL_CERT_PATH"
log_info "PERM" " [OK] SSL cert ownership -> root:$GA_GROUP, mode 640"
fi
if [ -n "$SSL_KEY_PATH" ] && [ -f "$SSL_KEY_PATH" ]; then
chown "root:$GA_GROUP" "$SSL_KEY_PATH"
chmod 640 "$SSL_KEY_PATH"
log_info "PERM" " [OK] SSL key ownership -> root:$GA_GROUP, mode 640"
fi
if [ -n "$SSL_CA_CERT_PATH" ] && [ -f "$SSL_CA_CERT_PATH" ]; then
chown "root:$GA_GROUP" "$SSL_CA_CERT_PATH"
chmod 640 "$SSL_CA_CERT_PATH"
log_info "PERM" " [OK] SSL CA cert ownership -> root:$GA_GROUP, mode 640"
fi
fi
if [ -f "$IFCFG_TEMPLATE_DIR/ifcfg-template" ]; then
chown "$GA_USER:$GA_GROUP" "$IFCFG_TEMPLATE_DIR/ifcfg-template"
chmod 640 "$IFCFG_TEMPLATE_DIR/ifcfg-template"
log_info "PERM" " [OK] ifcfg-template ownership -> $GA_USER:$GA_GROUP, mode 640"
fi
chown "root:$GA_GROUP" "$IFCFG_TEMPLATE_DIR"
chmod 750 "$IFCFG_TEMPLATE_DIR"
log_info "PERM" " [OK] $IFCFG_TEMPLATE_DIR ownership -> root:$GA_GROUP, mode 750"
chown "$GA_USER:$GA_GROUP" "$MONITOR_LOG_DIR"
chmod 755 "$MONITOR_LOG_DIR"
log_info "PERM" " [OK] $MONITOR_LOG_DIR ownership -> $GA_USER:$GA_GROUP, mode 755"
chown "$GA_USER:$GA_GROUP" "$MONITOR_CACHE_DIR"
chmod 755 "$MONITOR_CACHE_DIR"
log_info "PERM" " [OK] $MONITOR_CACHE_DIR ownership -> $GA_USER:$GA_GROUP, mode 755"
chown root:root "$NETWORK_SCRIPTS_DIR"
chmod 755 "$NETWORK_SCRIPTS_DIR"
log_info "PERM" " [OK] $NETWORK_SCRIPTS_DIR ownership -> root:root, mode 755"
if [ -f "/etc/systemd/system/virtcca-guest-agent.service" ]; then
chown root:root "/etc/systemd/system/virtcca-guest-agent.service"
chmod 644 "/etc/systemd/system/virtcca-guest-agent.service"
log_info "PERM" " [OK] service file ownership -> root:root, mode 644"
fi
log_info "PERM" "File permissions and ownership configured"
}
verify_installation() {
log_step "VERIFY" "Installation Verification"
local verify_pass=true
local ga_dir="$INSTALL_DIR/virtcca_deploy/guest_agent"
if [ -d "$ga_dir" ]; then
log_info "VERIFY" " [PASS] guest_agent/ directory exists"
else
log_error "VERIFY" " [FAIL] guest_agent/ directory missing"
verify_pass=false
fi
if [ -d "$ga_dir/app" ]; then
log_info "VERIFY" " [PASS] app/ directory exists"
else
log_error "VERIFY" " [FAIL] app/ directory missing"
verify_pass=false
fi
if [ -f "$ga_dir/main.py" ]; then
log_info "VERIFY" " [PASS] main.py exists"
else
log_error "VERIFY" " [FAIL] main.py missing"
verify_pass=false
fi
if [ -f "$ga_dir/.env" ]; then
local env_mode
env_mode=$(stat -c '%a' "$ga_dir/.env")
if [ "$env_mode" = "600" ]; then
log_info "VERIFY" " [PASS] .env exists with correct permissions (600)"
else
log_warn "VERIFY" " [WARN] .env has unexpected permissions: $env_mode (expected 600)"
fi
else
log_error "VERIFY" " [FAIL] .env missing"
verify_pass=false
fi
if [ -f "$ga_dir/requirements.txt" ]; then
log_info "VERIFY" " [PASS] requirements.txt exists"
else
log_error "VERIFY" " [FAIL] requirements.txt missing"
verify_pass=false
fi
if [ -f "$ga_dir/venv/bin/python" ]; then
log_info "VERIFY" " [PASS] Python venv exists"
else
log_error "VERIFY" " [FAIL] Python venv missing"
verify_pass=false
fi
if [ -d "$INSTALL_DIR/virtcca_deploy/monitor" ]; then
log_info "VERIFY" " [PASS] monitor/ directory exists"
else
log_error "VERIFY" " [FAIL] monitor/ directory missing"
verify_pass=false
fi
if [ -f "$INSTALL_DIR/virtcca_deploy/monitor/main.py" ]; then
log_info "VERIFY" " [PASS] monitor/main.py exists"
else
log_error "VERIFY" " [FAIL] monitor/main.py missing"
verify_pass=false
fi
if [ -f "/etc/systemd/system/virtcca-guest-agent.service" ]; then
log_info "VERIFY" " [PASS] systemd service file installed"
else
log_warn "VERIFY" " [SKIP] systemd service file not installed"
fi
if [ -d "$ga_dir/logs" ]; then
log_info "VERIFY" " [PASS] logs directory exists"
else
log_error "VERIFY" " [FAIL] logs directory missing"
verify_pass=false
fi
if id "$GA_USER" &>/dev/null; then
log_info "VERIFY" " [PASS] service user $GA_USER exists"
else
log_error "VERIFY" " [FAIL] service user $GA_USER not found"
verify_pass=false
fi
if [ -f "/etc/sudoers.d/virtcca-guest-agent" ]; then
log_info "VERIFY" " [PASS] sudoers whitelist installed"
else
log_error "VERIFY" " [FAIL] sudoers whitelist not found"
verify_pass=false
fi
if [ -d "$MONITOR_LOG_DIR" ]; then
log_info "VERIFY" " [PASS] monitor log directory exists"
else
log_error "VERIFY" " [FAIL] monitor log directory missing"
verify_pass=false
fi
return $( [ "$verify_pass" = true ] && echo 0 || echo 1 )
}
main() {
step1_init
step2_install_dependencies
step3_create_directories
step4_copy_files
step5_setup_python
step6_generate_config
step7_install_service
step8_create_user_and_sudoers
step9_set_permissions
if verify_installation; then
log_info "MAIN" "=========================================="
log_info "MAIN" "Guest Agent Setup Completed Successfully"
log_info "MAIN" "=========================================="
else
log_error "MAIN" "=========================================="
log_error "MAIN" "Guest Agent Setup Completed with Errors"
log_error "MAIN" "=========================================="
exit 1
fi
local end_time
end_time=$(date +%s)
local total_time=$((end_time - START_TIME))
log_info "MAIN" "Total duration: ${total_time}s"
log_info "MAIN" "Warnings: $WARN_COUNT"
log_info "MAIN" "Errors: $ERROR_COUNT"
}
main "$@"