Files
Redflag/aggregator-server/internal/services/templates/install/scripts/linux.sh.tmpl
jpetree331 23a4f5f3c1 feat(installer): arch detection + checksum verification
- Fix Windows config path in template (carry-over from Fix 1)
- Add runtime arch detection in Linux installer (uname -m)
- Add runtime arch detection in Windows installer
- Add ?arch= query param to install endpoint
- Serve X-Content-SHA256 header with binary downloads
- Verify checksum in Linux and Windows installers
- Warn (not fail) if server does not provide checksum

166 tests pass (106 server + 60 agent). No regressions.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-29 18:07:21 -04:00

400 lines
14 KiB
Bash

#!/bin/bash
# RedFlag Agent Installer - Linux
# Generated for agent: {{.AgentID}}
# Platform: {{.Platform}}
# Architecture: {{.Architecture}}
# Version: {{.Version}}
set -e
# Check if running as root (required for user creation and sudoers)
if [ "$EUID" -ne 0 ]; then
echo "ERROR: This script must be run as root for secure installation (use sudo)"
exit 1
fi
# Variables
AGENT_ID="{{.AgentID}}"
AGENT_USER="redflag-agent"
AGENT_HOME="{{.AgentHome}}"
BASE_DIR="/var/lib/redflag"
CONFIG_DIR="/etc/redflag"
AGENT_CONFIG_DIR="/etc/redflag/agent"
OLD_CONFIG_DIR="/etc/aggregator"
LOG_DIR="/var/log/redflag"
AGENT_LOG_DIR="/var/log/redflag/agent"
INSTALL_DIR="/usr/local/bin"
SERVICE_NAME="redflag-agent"
SUDOERS_FILE="/etc/sudoers.d/redflag-agent"
BINARY_URL="{{.BinaryURL}}"
CONFIG_URL="{{.ConfigURL}}"
VERSION="{{.Version}}"
BACKUP_DIR="${CONFIG_DIR}/backups/backup.$(date +%s)"
# Detect architecture
ARCH=$(uname -m)
case $ARCH in
x86_64) ARCH_TAG="amd64" ;;
aarch64) ARCH_TAG="arm64" ;;
armv7l) ARCH_TAG="armv7" ;;
*)
echo "Unsupported architecture: $ARCH"
echo "Supported: x86_64 (amd64), aarch64 (arm64), armv7l (armv7)"
exit 1
;;
esac
# Override download URL with detected architecture
BINARY_URL="{{.ServerURL}}/api/v1/downloads/linux-${ARCH_TAG}?version={{.Version}}"
# Function to detect package manager
detect_package_manager() {
if command -v apt-get &> /dev/null; then
echo "apt"
elif command -v dnf &> /dev/null; then
echo "dnf"
elif command -v yum &> /dev/null; then
echo "yum"
elif command -v pacman &> /dev/null; then
echo "pacman"
elif command -v zypper &> /dev/null; then
echo "zypper"
else
echo "unknown"
fi
}
echo "=== RedFlag Agent v${VERSION} Installation ==="
echo "Agent ID: ${AGENT_ID}"
echo "Platform: {{.Platform}}"
echo "Installing to: ${INSTALL_DIR}/${SERVICE_NAME}"
echo
# Step 1: Detect existing installation
echo "Detecting existing RedFlag installations..."
MIGRATION_NEEDED=false
if [ -f "${CONFIG_DIR}/config.json" ]; then
echo "✓ Existing installation detected at ${CONFIG_DIR}"
MIGRATION_NEEDED=true
elif [ -f "${OLD_CONFIG_DIR}/config.json" ]; then
echo "⚠ Old installation detected at ${OLD_CONFIG_DIR} - MIGRATION REQUIRED"
MIGRATION_NEEDED=true
else
echo "✓ Fresh installation"
fi
# Step 2: Create backup if migration needed
if [ "${MIGRATION_NEEDED}" = true ]; then
echo
echo "=== Migration Required ==="
echo "Agent will migrate on first start. Backing up configuration..."
sudo mkdir -p "${BACKUP_DIR}"
if [ -f "${OLD_CONFIG_DIR}/config.json" ]; then
echo "Backing up old configuration..."
sudo cp -r "${OLD_CONFIG_DIR}"/* "${BACKUP_DIR}/" 2>/dev/null || true
fi
if [ -f "${CONFIG_DIR}/config.json" ]; then
echo "Backing up current configuration..."
sudo cp "${CONFIG_DIR}/config.json" "${BACKUP_DIR}/config.json.backup" 2>/dev/null || true
fi
echo "Migration will run automatically when agent starts."
echo "View migration logs with: sudo journalctl -u ${SERVICE_NAME} -f"
echo
fi
# Step 3: Create system user and home directory
echo "Creating system user for agent..."
if id "$AGENT_USER" &>/dev/null; then
echo "✓ User $AGENT_USER already exists"
else
sudo useradd -r -s /bin/false -d "$AGENT_HOME" "$AGENT_USER"
echo "✓ User $AGENT_USER created"
fi
# Create home directory structure
if [ ! -d "$AGENT_HOME" ]; then
# Create nested directory structure
sudo mkdir -p "$BASE_DIR"
sudo mkdir -p "$AGENT_HOME"
sudo mkdir -p "$AGENT_HOME/cache"
sudo mkdir -p "$AGENT_HOME/state"
sudo mkdir -p "$AGENT_CONFIG_DIR"
sudo mkdir -p "$AGENT_LOG_DIR"
# Set ownership and permissions
sudo chown -R "$AGENT_USER:$AGENT_USER" "$BASE_DIR"
sudo chmod 750 "$BASE_DIR"
sudo chmod 750 "$AGENT_HOME"
sudo chmod 750 "$AGENT_HOME/cache"
sudo chmod 750 "$AGENT_HOME/state"
sudo chmod 755 "$AGENT_CONFIG_DIR"
sudo chmod 755 "$AGENT_LOG_DIR"
echo "✓ Agent directory structure created:"
echo " - Agent home: $AGENT_HOME"
echo " - Config: $AGENT_CONFIG_DIR"
echo " - Logs: $AGENT_LOG_DIR"
fi
# Step 4: Install sudoers configuration with OS-specific commands
PM=$(detect_package_manager)
echo "Detected package manager: $PM"
echo "Installing sudoers configuration..."
case "$PM" in
apt)
cat <<'EOF' | sudo tee "$SUDOERS_FILE" > /dev/null
# RedFlag Agent minimal sudo permissions - APT
{{.AgentUser}} ALL=(root) NOPASSWD: /usr/bin/apt-get update
{{.AgentUser}} ALL=(root) NOPASSWD: /usr/bin/apt-get install -y *
{{.AgentUser}} ALL=(root) NOPASSWD: /usr/bin/apt-get upgrade -y
{{.AgentUser}} ALL=(root) NOPASSWD: /usr/bin/apt-get install --dry-run --yes *
EOF
;;
dnf|yum)
cat <<'EOF' | sudo tee "$SUDOERS_FILE" > /dev/null
# RedFlag Agent minimal sudo permissions - DNF/YUM
{{.AgentUser}} ALL=(root) NOPASSWD: /usr/bin/dnf makecache
{{.AgentUser}} ALL=(root) NOPASSWD: /usr/bin/dnf install -y *
{{.AgentUser}} ALL=(root) NOPASSWD: /usr/bin/dnf upgrade -y
{{.AgentUser}} ALL=(root) NOPASSWD: /usr/bin/yum makecache
{{.AgentUser}} ALL=(root) NOPASSWD: /usr/bin/yum install -y *
{{.AgentUser}} ALL=(root) NOPASSWD: /usr/bin/yum update -y
EOF
;;
pacman)
cat <<'EOF' | sudo tee "$SUDOERS_FILE" > /dev/null
# RedFlag Agent minimal sudo permissions - Pacman
{{.AgentUser}} ALL=(root) NOPASSWD: /usr/bin/pacman -Sy
{{.AgentUser}} ALL=(root) NOPASSWD: /usr/bin/pacman -S --noconfirm *
EOF
;;
*)
cat <<'EOF' | sudo tee "$SUDOERS_FILE" > /dev/null
# RedFlag Agent minimal sudo permissions - Generic (APT and DNF)
{{.AgentUser}} ALL=(root) NOPASSWD: /usr/bin/apt-get update
{{.AgentUser}} ALL=(root) NOPASSWD: /usr/bin/apt-get install -y *
{{.AgentUser}} ALL=(root) NOPASSWD: /usr/bin/dnf makecache
{{.AgentUser}} ALL=(root) NOPASSWD: /usr/bin/dnf install -y *
EOF
;;
esac
# Add Docker commands
cat <<'DOCKER_EOF' | sudo tee -a "$SUDOERS_FILE" > /dev/null
{{.AgentUser}} ALL=(root) NOPASSWD: /usr/bin/docker pull *
{{.AgentUser}} ALL=(root) NOPASSWD: /usr/bin/docker image inspect *
{{.AgentUser}} ALL=(root) NOPASSWD: /usr/bin/docker manifest inspect *
DOCKER_EOF
sudo chmod 440 "$SUDOERS_FILE"
if visudo -c -f "$SUDOERS_FILE" &>/dev/null; then
echo "✓ Sudoers configuration installed and validated"
else
echo "⚠ Sudoers configuration validation failed - using generic version"
fi
# Step 5: Stop existing service
if systemctl is-active --quiet ${SERVICE_NAME} 2>/dev/null; then
echo "Stopping existing RedFlag agent service..."
sudo systemctl stop ${SERVICE_NAME}
fi
# Step 6: Create directories
echo "Creating directories..."
sudo mkdir -p "${AGENT_CONFIG_DIR}"
sudo mkdir -p "${CONFIG_DIR}/backups" # Legacy backup location
sudo mkdir -p "$AGENT_HOME"
sudo mkdir -p "$AGENT_LOG_DIR"
# Step 7: Download agent binary
echo "Downloading agent binary..."
TMP_BINARY=$(mktemp)
TMP_HEADERS=$(mktemp)
curl -fsSL -o "$TMP_BINARY" -D "$TMP_HEADERS" "${BINARY_URL}"
# Verify checksum if server provided one
EXPECTED_CHECKSUM=$(grep -i "x-content-sha256" "$TMP_HEADERS" | awk '{print $2}' | tr -d '\r\n')
if [ -n "$EXPECTED_CHECKSUM" ]; then
ACTUAL_CHECKSUM=$(sha256sum "$TMP_BINARY" | awk '{print $1}')
if [ "$EXPECTED_CHECKSUM" != "$ACTUAL_CHECKSUM" ]; then
echo "ERROR: Checksum verification failed"
echo "Expected: $EXPECTED_CHECKSUM"
echo "Actual: $ACTUAL_CHECKSUM"
rm -f "$TMP_BINARY" "$TMP_HEADERS"
exit 1
fi
echo "Checksum verified: $ACTUAL_CHECKSUM"
else
echo "WARNING: Server did not provide checksum header. Proceeding without verification."
fi
rm -f "$TMP_HEADERS"
sudo mv "$TMP_BINARY" "${INSTALL_DIR}/${SERVICE_NAME}"
sudo chmod +x "${INSTALL_DIR}/${SERVICE_NAME}"
# Step 8: Handle configuration
# IMPORTANT: The agent handles its own migration on first start.
# We either preserve existing config OR create a minimal template.
if [ -f "${AGENT_CONFIG_DIR}/config.json" ]; then
echo "[CONFIG] Upgrade detected - preserving existing configuration"
echo "[CONFIG] Agent will handle migration automatically on first start"
echo "[CONFIG] Backup created at: ${BACKUP_DIR}"
else
echo "[CONFIG] Fresh install - generating minimal configuration with registration token"
# Create minimal config template - agent will populate missing fields on first start
sudo tee "${AGENT_CONFIG_DIR}/config.json" > /dev/null <<EOF
{
"version": 5,
"agent_version": "${VERSION}",
"agent_id": "",
"token": "",
"refresh_token": "",
"registration_token": "{{.RegistrationToken}}",
"machine_id": "",
"check_in_interval": 300,
"server_url": "{{.ServerURL}}",
"network": {
"timeout": 30000000000,
"retry_count": 3,
"retry_delay": 5000000000,
"max_idle_conn": 10
},
"proxy": {
"enabled": false
},
"tls": {
"enabled": false,
"insecure_skip_verify": false
},
"logging": {
"level": "info",
"max_size": 100,
"max_backups": 3,
"max_age": 28
},
"subsystems": {
"system": {"enabled": true, "timeout": 10000000000, "circuit_breaker": {"enabled": true, "failure_threshold": 3, "failure_window": 600000000000, "open_duration": 1800000000000, "half_open_attempts": 2}},
"filesystem": {"enabled": true, "timeout": 10000000000, "circuit_breaker": {"enabled": true, "failure_threshold": 3, "failure_window": 600000000000, "open_duration": 1800000000000, "half_open_attempts": 2}},
"network": {"enabled": true, "timeout": 30000000000, "circuit_breaker": {"enabled": true, "failure_threshold": 3, "failure_window": 600000000000, "open_duration": 1800000000000, "half_open_attempts": 2}},
"processes": {"enabled": true, "timeout": 30000000000, "circuit_breaker": {"enabled": true, "failure_threshold": 3, "failure_window": 600000000000, "open_duration": 1800000000000, "half_open_attempts": 2}},
"updates": {"enabled": true, "timeout": 30000000000, "circuit_breaker": {"enabled": false, "failure_threshold": 0, "failure_window": 0, "open_duration": 0, "half_open_attempts": 0}},
"storage": {"enabled": true, "timeout": 10000000000, "circuit_breaker": {"enabled": true, "failure_threshold": 3, "failure_window": 600000000000, "open_duration": 1800000000000, "half_open_attempts": 2}}
},
"security": {
"ed25519_verification": true,
"nonce_validation": true,
"machine_id_binding": true
}
}
EOF
fi
# Step 9: Set permissions on config file
sudo chmod 600 "${AGENT_CONFIG_DIR}/config.json"
# Step 10: Create systemd service with security hardening
echo "Creating systemd service with security configuration..."
cat <<EOF | sudo tee /etc/systemd/system/${SERVICE_NAME}.service
[Unit]
Description=RedFlag Security Agent
After=network.target
StartLimitBurst=5
StartLimitIntervalSec=60
[Service]
Type=simple
User={{.AgentUser}}
Group={{.AgentUser}}
WorkingDirectory={{.AgentHome}}
ExecStart=${INSTALL_DIR}/${SERVICE_NAME}
Restart=always
RestartSec=30
RestartPreventExitStatus=255
# Security hardening
# Note: NoNewPrivileges disabled to allow sudo for package management
ProtectSystem=strict
ProtectHome=true
ReadWritePaths={{.AgentHome}} {{.AgentHome}}/cache {{.AgentHome}}/state {{.AgentHome}}/migration_backups {{.AgentConfigDir}} {{.AgentLogDir}}
PrivateTmp=true
ProtectKernelTunables=true
ProtectKernelModules=true
ProtectControlGroups=true
RestrictRealtime=true
RestrictSUIDSGID=true
RemoveIPC=true
# Logging
StandardOutput=journal
StandardError=journal
SyslogIdentifier=${SERVICE_NAME}
[Install]
WantedBy=multi-user.target
EOF
# Set proper permissions on directories
echo "Setting directory permissions..."
sudo chown -R {{.AgentUser}}:{{.AgentUser}} "{{.AgentConfigDir}}"
sudo chown {{.AgentUser}}:{{.AgentUser}} "{{.AgentConfigDir}}/config.json"
sudo chmod 600 "{{.AgentConfigDir}}/config.json"
sudo chown -R {{.AgentUser}}:{{.AgentUser}} "{{.AgentHome}}"
sudo chmod 750 "{{.AgentHome}}"
sudo chown -R {{.AgentUser}}:{{.AgentUser}} "{{.AgentLogDir}}"
sudo chmod 750 "{{.AgentLogDir}}"
# Register agent with server (if token provided)
if [ -n "{{.RegistrationToken}}" ]; then
echo "[INFO] [installer] [register] Registering agent with server..."
if sudo -u "{{.AgentUser}}" "${INSTALL_DIR}/${SERVICE_NAME}" --server "{{.ServerURL}}" --token "{{.RegistrationToken}}" --register; then
echo "[SUCCESS] [installer] [register] Agent registered successfully"
echo "[INFO] [installer] [register] Agent ID assigned, configuration updated"
else
echo "[ERROR] [installer] [register] Registration failed - check token validity and server connectivity"
echo "[WARN] [installer] [register] Agent installed but not registered. Service will not start."
echo ""
echo "[INFO] [installer] [register] To retry registration manually:"
echo "[INFO] [installer] [register] sudo -u {{.AgentUser}} ${INSTALL_DIR}/${SERVICE_NAME} --server {{.ServerURL}} --token YOUR_TOKEN --register"
echo "[INFO] [installer] [register] Then start service:"
echo "[INFO] [installer] [register] sudo systemctl start ${SERVICE_NAME}"
exit 1
fi
else
echo "[INFO] [installer] [register] No registration token provided - skipping registration"
echo "[INFO] [installer] [register] Service will start but agent will exit until registered"
echo "[INFO] [installer] [register] To register manually:"
echo "[INFO] [installer] [register] sudo -u {{.AgentUser}} ${INSTALL_DIR}/${SERVICE_NAME} --server {{.ServerURL}} --token YOUR_TOKEN --register"
fi
# Step 11: Enable and start service
echo "Enabling and starting service..."
sudo systemctl daemon-reload
sudo systemctl enable ${SERVICE_NAME}
sudo systemctl start ${SERVICE_NAME}
echo
if systemctl is-active --quiet ${SERVICE_NAME}; then
echo "✓ Installation complete!"
echo ""
echo "=== Security Information ==="
echo "Agent is running with security hardening:"
echo " ✓ Dedicated system user: {{.AgentUser}}"
echo " ✓ Limited sudo access for package management only"
echo " ✓ Systemd service with security restrictions"
echo " ✓ Protected configuration directory"
echo ""
echo "Check status: sudo systemctl status ${SERVICE_NAME}"
echo "View logs: sudo journalctl -u ${SERVICE_NAME} -f"
else
echo "⚠ Installation complete but service not started"
echo " This may be normal for fresh installs awaiting registration"
echo ""
echo "To start after registration:"
echo " sudo systemctl start ${SERVICE_NAME}"
fi