365 lines
12 KiB
Bash
365 lines
12 KiB
Bash
#!/bin/bash
|
|
|
|
set -e
|
|
|
|
# ─── Colors ───────────────────────────────────────────────────────────────────
|
|
GREEN='\033[0;32m'
|
|
RED='\033[0;31m'
|
|
YELLOW='\033[1;33m'
|
|
BLUE='\033[0;34m'
|
|
NC='\033[0m'
|
|
|
|
log_ok() { echo -e "${GREEN}[+]${NC} $*"; }
|
|
log_err() { echo -e "${RED}[!]${NC} $*"; }
|
|
log_warn() { echo -e "${YELLOW}[~]${NC} $*"; }
|
|
log_info() { echo -e "${BLUE}[*]${NC} $*"; }
|
|
|
|
# ─── Logging to file (without passwords) ─────────────────────────────────────
|
|
LOG_FILE="/var/log/setup.log"
|
|
# Redirect to both terminal and log file, but we'll handle passwords separately
|
|
exec > >(tee -a "$LOG_FILE") 2>&1
|
|
log_info "Log saved to $LOG_FILE"
|
|
|
|
# ─── Help ─────────────────────────────────────────────────────────────────────
|
|
show_help() {
|
|
echo ""
|
|
echo "Usage: $0 [options]"
|
|
echo ""
|
|
echo "Options:"
|
|
echo " --user <username> User to create (default: user)"
|
|
echo " --password <password> Password for the user (default: auto-generated)"
|
|
echo " --timezone <tz> System timezone (default: UTC, e.g. Europe/Moscow)"
|
|
echo " --help Show this help message"
|
|
echo ""
|
|
echo "Examples:"
|
|
echo " $0"
|
|
echo " $0 --user admin"
|
|
echo " $0 --user admin --password MySecret123!"
|
|
echo " $0 --timezone Europe/Moscow"
|
|
echo ""
|
|
}
|
|
|
|
# ─── Parse arguments ──────────────────────────────────────────────────────────
|
|
NEW_USER="user"
|
|
USER_PASSWORD=""
|
|
TIMEZONE="UTC"
|
|
|
|
while [[ $# -gt 0 ]]; do
|
|
case "$1" in
|
|
--user)
|
|
NEW_USER="$2"
|
|
shift 2
|
|
;;
|
|
--password)
|
|
USER_PASSWORD="$2"
|
|
shift 2
|
|
;;
|
|
--timezone)
|
|
TIMEZONE="$2"
|
|
shift 2
|
|
;;
|
|
--help)
|
|
show_help
|
|
exit 0
|
|
;;
|
|
*)
|
|
log_err "Unknown argument: $1"
|
|
show_help
|
|
exit 1
|
|
;;
|
|
esac
|
|
done
|
|
|
|
# ─── Must be root ─────────────────────────────────────────────────────────────
|
|
if [[ $EUID -ne 0 ]]; then
|
|
log_err "This script must be run as root"
|
|
exit 1
|
|
fi
|
|
|
|
# ─── Validate OS ──────────────────────────────────────────────────────────────
|
|
if ! command -v apt &>/dev/null; then
|
|
log_err "This script requires a Debian/Ubuntu-based system (apt not found)"
|
|
exit 1
|
|
fi
|
|
log_ok "OS check passed"
|
|
|
|
# ─── Validate timezone ────────────────────────────────────────────────────────
|
|
if ! timedatectl list-timezones | grep -qx "$TIMEZONE"; then
|
|
log_err "Invalid timezone: '$TIMEZONE'"
|
|
log_info "Example valid timezones: UTC, Europe/Moscow, America/New_York"
|
|
exit 1
|
|
fi
|
|
log_ok "Timezone validation passed: $TIMEZONE"
|
|
|
|
# ─── Generate password if not provided ───────────────────────────────────────
|
|
generate_password() {
|
|
local upper lower special digits all
|
|
upper="ABCDEFGHIJKLMNOPQRSTUVWXYZ"
|
|
lower="abcdefghijklmnopqrstuvwxyz"
|
|
special="!@#\$%^&*()-_=+[]{}|;:,.<>?"
|
|
digits="0123456789"
|
|
all="${upper}${lower}${special}${digits}"
|
|
|
|
local pass=""
|
|
pass+=$(tr -dc "$upper" < /dev/urandom | head -c 3)
|
|
pass+=$(tr -dc "$lower" < /dev/urandom | head -c 3)
|
|
pass+=$(tr -dc "$special" < /dev/urandom | head -c 3)
|
|
pass+=$(tr -dc "$digits" < /dev/urandom | head -c 2)
|
|
pass+=$(tr -dc "$all" < /dev/urandom | head -c 4)
|
|
|
|
# Shuffle so groups don't appear in order
|
|
echo "$pass" | fold -w1 | shuf | tr -d '\n'
|
|
}
|
|
|
|
if [[ -z "$USER_PASSWORD" ]]; then
|
|
USER_PASSWORD=$(generate_password)
|
|
log_ok "Password auto-generated"
|
|
else
|
|
log_info "Using provided password"
|
|
fi
|
|
|
|
log_info "Username : $NEW_USER"
|
|
log_info "Timezone : $TIMEZONE"
|
|
|
|
# ─── 1. System update ─────────────────────────────────────────────────────────
|
|
log_ok "Updating system..."
|
|
apt update && apt upgrade -y
|
|
|
|
# ─── 2. Set timezone ──────────────────────────────────────────────────────────
|
|
log_ok "Setting timezone to $TIMEZONE..."
|
|
timedatectl set-timezone "$TIMEZONE"
|
|
log_ok "Timezone set to $TIMEZONE"
|
|
|
|
# ─── 3. Install ufw ───────────────────────────────────────────────────────────
|
|
log_ok "Installing ufw..."
|
|
apt install -y ufw
|
|
|
|
ufw default deny incoming
|
|
ufw default allow outgoing
|
|
|
|
# Always ensure SSH is open before enabling UFW
|
|
if ! ufw status | grep -q '22/tcp'; then
|
|
log_warn "SSH port (22) not found in UFW rules, adding it now to prevent lockout..."
|
|
ufw allow 22/tcp
|
|
fi
|
|
|
|
ufw allow 9090/tcp
|
|
ufw --force enable
|
|
log_ok "ufw enabled. Open ports: 22/tcp, 9090/tcp"
|
|
|
|
# ─── 4. Install fail2ban ──────────────────────────────────────────────────────
|
|
log_ok "Installing fail2ban..."
|
|
apt install -y fail2ban
|
|
|
|
cat > /etc/fail2ban/jail.local <<EOF
|
|
[DEFAULT]
|
|
bantime = 1h
|
|
findtime = 10m
|
|
maxretry = 5
|
|
|
|
[sshd]
|
|
enabled = true
|
|
port = 22
|
|
|
|
[cockpit]
|
|
enabled = true
|
|
port = 9090
|
|
filter = cockpit
|
|
logpath = /var/log/auth.log
|
|
maxretry = 5
|
|
EOF
|
|
|
|
cat > /etc/fail2ban/filter.d/cockpit.conf <<EOF
|
|
[Definition]
|
|
failregex = ^.*cockpit.*authentication failure.*rhost=<HOST>.*$
|
|
^.*pam_unix.*cockpit.*authentication failure.*rhost=<HOST>.*$
|
|
ignoreregex =
|
|
maxlines = 1
|
|
allowipv6 = auto
|
|
EOF
|
|
|
|
systemctl enable fail2ban
|
|
systemctl restart fail2ban
|
|
log_ok "fail2ban installed and configured"
|
|
|
|
# ─── 5. Create user with sudo rights ─────────────────────────────────────────
|
|
log_ok "Creating user '$NEW_USER'..."
|
|
if id "$NEW_USER" &>/dev/null; then
|
|
log_warn "User '$NEW_USER' already exists — updating password and sudo rights"
|
|
usermod -aG sudo "$NEW_USER"
|
|
else
|
|
adduser --disabled-password --gecos "" "$NEW_USER"
|
|
usermod -aG sudo "$NEW_USER"
|
|
fi
|
|
|
|
# Use printf to safely handle special characters in password
|
|
printf '%s:%s\n' "$NEW_USER" "$USER_PASSWORD" | chpasswd
|
|
|
|
# Save password to file, excluded from log output
|
|
{
|
|
echo "User: $NEW_USER"
|
|
echo "Password: $USER_PASSWORD"
|
|
} > /root/${NEW_USER}_password.txt
|
|
chmod 600 /root/${NEW_USER}_password.txt
|
|
|
|
log_ok "User '$NEW_USER' ready. Credentials saved to /root/${NEW_USER}_password.txt"
|
|
|
|
# ─── 6. Install Cockpit ───────────────────────────────────────────────────────
|
|
log_ok "Installing Cockpit..."
|
|
apt install -y cockpit
|
|
|
|
systemctl enable cockpit.socket
|
|
systemctl start cockpit.socket
|
|
|
|
# ─── 7. Create certificate script ─────────────────────────────────────────────
|
|
log_ok "Creating certificate script..."
|
|
|
|
CERT_SCRIPT="/usr/local/bin/selfcert-renew.sh"
|
|
|
|
cat > "$CERT_SCRIPT" <<'CERTSCRIPT'
|
|
#!/bin/bash
|
|
|
|
set -e
|
|
|
|
DOMAIN_OR_CN="$(hostname)"
|
|
CERT_DIR="/etc/ssl/selfcert"
|
|
CERT="$CERT_DIR/cert.pem"
|
|
KEY="$CERT_DIR/key.pem"
|
|
|
|
COCKPIT_CERT_DIR="/etc/cockpit/ws-certs.d"
|
|
COCKPIT_CERT="$COCKPIT_CERT_DIR/01-self.cert"
|
|
COCKPIT_KEY="$COCKPIT_CERT_DIR/01-self.key"
|
|
|
|
DAYS_VALID=365
|
|
RENEW_BEFORE_DAYS=7
|
|
|
|
mkdir -p "$CERT_DIR"
|
|
mkdir -p "$COCKPIT_CERT_DIR"
|
|
|
|
echo "[+] Using CN: $DOMAIN_OR_CN"
|
|
|
|
generate_cert() {
|
|
echo "[+] Generating certificate..."
|
|
|
|
openssl req -x509 -newkey rsa:4096 -nodes \
|
|
-keyout "$KEY" \
|
|
-out "$CERT" \
|
|
-days "$DAYS_VALID" \
|
|
-subj "/CN=$DOMAIN_OR_CN"
|
|
|
|
chmod 600 "$KEY"
|
|
chmod 644 "$CERT"
|
|
|
|
echo "[+] Certificate generated"
|
|
}
|
|
|
|
deploy_cert() {
|
|
echo "[+] Deploying certificate to Cockpit..."
|
|
|
|
cp "$CERT" "$COCKPIT_CERT"
|
|
cp "$KEY" "$COCKPIT_KEY"
|
|
|
|
chmod 644 "$COCKPIT_CERT"
|
|
chmod 640 "$COCKPIT_KEY"
|
|
|
|
# cockpit-ws group may not exist on all systems
|
|
if getent group cockpit-ws &>/dev/null; then
|
|
chown root:cockpit-ws "$COCKPIT_KEY"
|
|
else
|
|
echo "[~] Group cockpit-ws not found, skipping chown"
|
|
fi
|
|
|
|
echo "[+] Certificate deployed to $COCKPIT_CERT_DIR"
|
|
}
|
|
|
|
need_renew() {
|
|
if [ ! -f "$CERT" ]; then
|
|
return 0
|
|
fi
|
|
|
|
local expiry
|
|
expiry=$(openssl x509 -enddate -noout -in "$CERT" | cut -d= -f2)
|
|
local expiry_sec
|
|
expiry_sec=$(date -d "$expiry" +%s)
|
|
local now_sec
|
|
now_sec=$(date +%s)
|
|
|
|
local left_days=$(( (expiry_sec - now_sec) / 86400 ))
|
|
|
|
echo "[+] Days left: $left_days"
|
|
|
|
if [ "$left_days" -le "$RENEW_BEFORE_DAYS" ]; then
|
|
return 0
|
|
fi
|
|
|
|
return 1
|
|
}
|
|
|
|
reload_services() {
|
|
echo "[+] Reloading services..."
|
|
|
|
# Only reload nginx if it's actually running
|
|
if systemctl is-active --quiet nginx; then
|
|
systemctl reload nginx
|
|
echo "[+] nginx reloaded"
|
|
fi
|
|
|
|
systemctl restart cockpit 2>/dev/null || true
|
|
|
|
echo "[+] Services reloaded"
|
|
}
|
|
|
|
# ── main ──
|
|
if [ ! -f "$CERT" ] || need_renew; then
|
|
generate_cert
|
|
deploy_cert
|
|
reload_services
|
|
else
|
|
echo "[+] Certificate still valid, nothing to do"
|
|
fi
|
|
CERTSCRIPT
|
|
|
|
chmod +x "$CERT_SCRIPT"
|
|
log_ok "Certificate script created at $CERT_SCRIPT"
|
|
|
|
bash "$CERT_SCRIPT"
|
|
|
|
# ─── 8. Create cron job ───────────────────────────────────────────────────────
|
|
log_ok "Setting up daily cron job for certificate renewal..."
|
|
|
|
cat > /etc/cron.d/selfcert-renew <<EOF
|
|
0 3 * * * root /usr/local/bin/selfcert-renew.sh >> /var/log/selfcert-renew.log 2>&1
|
|
EOF
|
|
|
|
chmod 644 /etc/cron.d/selfcert-renew
|
|
log_ok "Cron job created at /etc/cron.d/selfcert-renew"
|
|
|
|
# ─── 9. Print summary ────────────────────────────────────────────────────────
|
|
SERVER_IP=$(hostname -I | awk '{print $1}')
|
|
|
|
# Stop logging to file before printing sensitive info
|
|
exec 1>/dev/tty 2>/dev/tty
|
|
|
|
echo ""
|
|
echo -e "${GREEN}════════════════════════════════════════════════${NC}"
|
|
echo -e "${GREEN} ✅ Setup complete!${NC}"
|
|
echo -e "${GREEN}════════════════════════════════════════════════${NC}"
|
|
echo ""
|
|
echo -e " 🌐 ${BLUE}Cockpit URL:${NC}"
|
|
echo -e " https://${SERVER_IP}:9090"
|
|
echo ""
|
|
echo -e " 👤 ${BLUE}User credentials:${NC}"
|
|
echo -e " Login: ${NEW_USER}"
|
|
echo -e " Password: ${USER_PASSWORD}"
|
|
echo ""
|
|
echo -e " 🛡️ ${BLUE}fail2ban:${NC} active (SSH + Cockpit)"
|
|
echo -e " 🕐 ${BLUE}Timezone:${NC} ${TIMEZONE}"
|
|
echo -e " 📄 ${BLUE}Certificate:${NC} /etc/ssl/selfcert/cert.pem"
|
|
echo -e " 🔑 ${BLUE}Key:${NC} /etc/ssl/selfcert/key.pem"
|
|
echo -e " 📋 ${BLUE}Setup log:${NC} $LOG_FILE"
|
|
echo -e " 📋 ${BLUE}Cert log:${NC} /var/log/selfcert-renew.log"
|
|
echo ""
|
|
echo -e " ${YELLOW}⚠️ After saving your password, delete the file:${NC}"
|
|
echo -e " ${YELLOW}rm -f /root/${NEW_USER}_password.txt${NC}"
|
|
echo ""
|
|
echo -e "${GREEN}════════════════════════════════════════════════${NC}" |