336 lines
13 KiB
Bash
336 lines
13 KiB
Bash
# Updates APT packages and installs required system dependencies.
|
|
function systemApt() {
|
|
apt update && apt upgrade -y || return 1
|
|
apt install -y curl ipset iptables-persistent ipset-persistent jq zip mc nano idn2 acl xfsprogs opendkim-tools pigz
|
|
}
|
|
|
|
# Creates ipset sets for WEDOS, WEDOS Global, and whitelist traffic, and installs an hourly update cron job.
|
|
function systemIpset() {
|
|
ipset list wedos &>/dev/null || ipset create wedos hash:ip family inet || {
|
|
appError "Failed create ipset: wedos"
|
|
return 1
|
|
}
|
|
ipset list wedos6 &>/dev/null || ipset create wedos6 hash:ip family inet6 || {
|
|
appError "Failed create ipset: wedos6"
|
|
return 1
|
|
}
|
|
ipset list wedos-global &>/dev/null || ipset create wedos-global hash:net family inet || {
|
|
appError "Failed create ipset: wedos-global"
|
|
return 1
|
|
}
|
|
ipset list wedos-global6 &>/dev/null || ipset create wedos-global6 hash:net family inet6 || {
|
|
appError "Failed create ipset: wedos-global6"
|
|
return 1
|
|
}
|
|
ipset list whitelist &>/dev/null || ipset create whitelist hash:ip family inet || {
|
|
appError "Failed create ipset: whitelist"
|
|
return 1
|
|
}
|
|
ipset list whitelist6 &>/dev/null || ipset create whitelist6 hash:ip family inet6 || {
|
|
appError "Failed create ipset: whitelist6"
|
|
return 1
|
|
}
|
|
|
|
ipset add wedos 46.28.104.66 -exist
|
|
ipset add wedos 46.28.107.200 -exist
|
|
ipset add wedos 46.28.104.146 -exist
|
|
ipset add wedos 46.28.107.215 -exist
|
|
|
|
local cron="/etc/cron.d/wedos-global-update"
|
|
local job='0 * * * * root curl -fsSL https://ips.wedos.global/ips.json | jq -r '"'"'.list[]'"'"' | while read -r ip; do [[ "$ip" == *:* ]] && ipset add wedos-global6 "$ip" -exist || ipset add wedos-global "$ip" -exist; done >> /var/log/wedos-ipset-update.log 2>&1'
|
|
printf '%s\n' "$job" > "$cron" || {
|
|
appError "Failed write cron file: $cron"
|
|
return 1
|
|
}
|
|
chmod 644 "$cron" || {
|
|
appError "Failed chmod cron file: $cron"
|
|
return 1
|
|
}
|
|
|
|
(set -o pipefail; curl -fsSL https://ips.wedos.global/ips.json | jq -r '.list[]' | while read -r ip; do
|
|
[[ "$ip" == *:* ]] && ipset add wedos-global6 "$ip" -exist || ipset add wedos-global "$ip" -exist
|
|
done) >> /var/log/wedos-ipset-update.log 2>&1 || appError "Failed to update WEDOS Global IP sets."
|
|
}
|
|
|
|
# Installs persistent iptables and ip6tables INPUT rules, then saves and reloads via netfilter-persistent.
|
|
function systemIptables() {
|
|
iptables -C INPUT -m set --match-set wedos src -j ACCEPT 2>/dev/null || iptables -I INPUT 1 -m set --match-set wedos src -j ACCEPT
|
|
ip6tables -C INPUT -m set --match-set wedos6 src -j ACCEPT 2>/dev/null || ip6tables -I INPUT 1 -m set --match-set wedos6 src -j ACCEPT
|
|
|
|
iptables -C INPUT -m set --match-set wedos-global src -p tcp -m multiport --dports 80,443 -j ACCEPT 2>/dev/null || \
|
|
iptables -I INPUT 2 -m set --match-set wedos-global src -p tcp -m multiport --dports 80,443 -j ACCEPT
|
|
ip6tables -C INPUT -m set --match-set wedos-global6 src -p tcp -m multiport --dports 80,443 -j ACCEPT 2>/dev/null || \
|
|
ip6tables -I INPUT 2 -m set --match-set wedos-global6 src -p tcp -m multiport --dports 80,443 -j ACCEPT
|
|
|
|
iptables -C INPUT -m set --match-set whitelist src -p tcp -m multiport --dports 80,443 -j ACCEPT 2>/dev/null || \
|
|
iptables -I INPUT 3 -m set --match-set whitelist src -p tcp -m multiport --dports 80,443 -j ACCEPT
|
|
ip6tables -C INPUT -m set --match-set whitelist6 src -p tcp -m multiport --dports 80,443 -j ACCEPT 2>/dev/null || \
|
|
ip6tables -I INPUT 3 -m set --match-set whitelist6 src -p tcp -m multiport --dports 80,443 -j ACCEPT
|
|
|
|
iptables -C INPUT -i lo -j ACCEPT 2>/dev/null || iptables -I INPUT 4 -i lo -j ACCEPT
|
|
ip6tables -C INPUT -i lo -j ACCEPT 2>/dev/null || ip6tables -I INPUT 4 -i lo -j ACCEPT
|
|
|
|
iptables -C INPUT -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT 2>/dev/null || \
|
|
iptables -I INPUT 5 -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT
|
|
ip6tables -C INPUT -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT 2>/dev/null || \
|
|
ip6tables -I INPUT 5 -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT
|
|
|
|
iptables -C INPUT -p tcp --dport 22 -m conntrack --ctstate NEW -j ACCEPT 2>/dev/null || \
|
|
iptables -I INPUT 6 -p tcp --dport 22 -m conntrack --ctstate NEW -j ACCEPT
|
|
ip6tables -C INPUT -p tcp --dport 22 -m conntrack --ctstate NEW -j ACCEPT 2>/dev/null || \
|
|
ip6tables -I INPUT 6 -p tcp --dport 22 -m conntrack --ctstate NEW -j ACCEPT
|
|
|
|
iptables -C INPUT -j DROP 2>/dev/null || iptables -A INPUT -j DROP
|
|
ip6tables -C INPUT -j DROP 2>/dev/null || ip6tables -A INPUT -j DROP
|
|
|
|
netfilter-persistent save || return 1
|
|
netfilter-persistent reload || return 1
|
|
}
|
|
|
|
# Returns non-empty root crontab lines; empty result if no crontab exists.
|
|
function systemCronList() {
|
|
local result
|
|
if result="$(crontab -u root -l 2>&1)"; then
|
|
:
|
|
elif grep -qi 'no crontab for' <<< "$result"; then
|
|
result=""
|
|
else
|
|
appError "Failed to retrieve cron jobs: $result"
|
|
return 1
|
|
fi
|
|
|
|
printf '%s\n' "$result" | awk 'NF'
|
|
}
|
|
|
|
# Returns the IP address for a domain from /etc/hosts.
|
|
# $1 (domain): domain name to look up.
|
|
function systemHostGet() {
|
|
local domain="$1"
|
|
[[ -n "$domain" ]] || { appError "Domain not specified"; return 1; }
|
|
|
|
awk -v domain="$domain" '
|
|
$1 !~ /^#/ {
|
|
for (i = 2; i <= NF; i++) {
|
|
if ($i == domain) {
|
|
print $1
|
|
exit
|
|
}
|
|
}
|
|
}
|
|
' /etc/hosts
|
|
}
|
|
|
|
# Adds a domain entry to /etc/hosts if not already present.
|
|
# $1 (domain): site domain name.
|
|
# [$2] (ip): IP address (defaults to 127.0.0.1).
|
|
function systemHostAdd() {
|
|
local domain
|
|
domain=$(domainPrepare "$1")
|
|
domainCheck "$domain" || return 1
|
|
|
|
local ip="${2:-127.0.0.1}"
|
|
|
|
if ! awk -v ip="$ip" -v domain="$domain" '$1 == ip && $2 == domain { found=1 } END { exit !found }' /etc/hosts; then
|
|
printf '%s %s\n' "$ip" "$domain" >> /etc/hosts || {
|
|
appError "Failed writing hosts"
|
|
return 1
|
|
}
|
|
fi
|
|
}
|
|
|
|
# Removes a domain entry from /etc/hosts.
|
|
# $1 (domain): site domain name.
|
|
# [$2] (ip): IP address (defaults to 127.0.0.1).
|
|
function systemHostRemove() {
|
|
local domain
|
|
domain=$(domainPrepare "$1")
|
|
domainCheck "$domain" || return 1
|
|
|
|
local ip="${2:-127.0.0.1}"
|
|
|
|
local tmp
|
|
tmp=$(mktemp) || return 1
|
|
|
|
if ! awk -v ip="$ip" -v domain="$domain" '!($1 == ip && $2 == domain)' /etc/hosts > "$tmp"; then
|
|
rm -f "$tmp"
|
|
appError "Failed editing hosts"
|
|
return 1
|
|
fi
|
|
|
|
mv -- "$tmp" /etc/hosts || {
|
|
rm -f "$tmp"
|
|
appError "Failed writing hosts"
|
|
return 1
|
|
}
|
|
}
|
|
|
|
# Lists users in the SFTP access group.
|
|
function sftpUserList() {
|
|
getent group "$sftpAccessGroup" | awk -F: '{print $4}' | tr ',' '\n' | sed '/^$/d' | sort
|
|
}
|
|
|
|
# Sets the SFTP password for a domain user from site config.
|
|
function sftpPasswordSet() {
|
|
local domain="$1"
|
|
domain=$(domainPrepare "$domain")
|
|
domainCheck "$domain" || return 1
|
|
[[ -n "$domain" && "$domain" != "root" ]] || { appError "Incorrect or empty domain: $domain"; return 1; }
|
|
|
|
local ug sftpPass
|
|
ug=$(domainToUser "$domain")
|
|
|
|
id "$ug" >/dev/null 2>&1 || { appError "User does not exist: $ug"; return 1; }
|
|
sftpPass=$(siteConfigGetOrCreate "$domain" "sftpPass")
|
|
[[ -n "$sftpPass" ]] || { appError "SFTP password is empty for domain: $domain"; return 1; }
|
|
|
|
printf '%s:%s\n' "$ug" "$sftpPass" | chpasswd || { appError "Change SFTP password failed for user: $ug"; return 1; }
|
|
}
|
|
|
|
# Enables SFTP access for a domain user.
|
|
function sftpAccessEnable() {
|
|
local domain="$1"
|
|
domain=$(domainPrepare "$domain")
|
|
domainCheck "$domain" || return 1
|
|
[[ -n "$domain" && "$domain" != "root" ]] || { appError "Incorrect or empty domain: $domain"; return 1; }
|
|
|
|
local output error ug
|
|
ug=$(domainToUser "$domain")
|
|
id "$ug" >/dev/null 2>&1 || { appError "User does not exist: $ug"; return 1; }
|
|
[[ -d "$olsVhostsPath/$domain/www" ]] || { appError "Vhost www directory does not exist: $olsVhostsPath/$domain/www"; return 1; }
|
|
|
|
if ! id -nG "$ug" | tr ' ' '\n' | grep -Fxq "$sftpAccessGroup"; then
|
|
run output error usermod -aG "$sftpAccessGroup" "$ug" || { appError "Add user $ug to $sftpAccessGroup: $error"; return 1; }
|
|
fi
|
|
|
|
sftpPasswordSet "$domain"
|
|
}
|
|
|
|
# Disables SFTP access for a domain user by removing them from the SFTP group.
|
|
function sftpAccessDisable() {
|
|
local domain="$1"
|
|
domain=$(domainPrepare "$domain")
|
|
domainCheck "$domain" || return 1
|
|
[[ -n "$domain" && "$domain" != "root" ]] || { appError "Incorrect or empty domain: $domain"; return 1; }
|
|
|
|
local output error ug
|
|
ug=$(domainToUser "$domain")
|
|
id "$ug" >/dev/null 2>&1 || { appError "User does not exist: $ug"; return 1; }
|
|
|
|
if id -nG "$ug" | tr ' ' '\n' | grep -Fxq "$sftpAccessGroup"; then
|
|
run output error gpasswd -d "$ug" "$sftpAccessGroup" || { appError "Remove user $ug from $sftpAccessGroup: $error"; return 1; }
|
|
fi
|
|
}
|
|
|
|
# [WARNING] Patches sshd_config to enable SFTP via internal-sftp with group-based chroot.
|
|
function sftpAddingSupport() {
|
|
local sftpConfig="/etc/ssh/sshd_config"
|
|
local sshService sshdBin sftpBackup output error
|
|
|
|
# printInfo "$systemLabel SFTP Adding support for SFTP access"
|
|
[[ -f "$sftpConfig" ]] || { appError "SFTP Config not found: $sftpConfig"; return 1; }
|
|
|
|
if systemctl list-unit-files | grep -q '^sshd\.service'; then
|
|
sshService="sshd"
|
|
elif systemctl list-unit-files | grep -q '^ssh\.service'; then
|
|
sshService="ssh"
|
|
else
|
|
appError "SFTP Service not found"
|
|
return 1
|
|
fi
|
|
# printInfo "$systemLabel SFTP Use service: $sshService"
|
|
|
|
sshdBin="$(command -v sshd || true)"
|
|
[[ -n "$sshdBin" ]] || { appError "SFTP Binary not found"; return 1; }
|
|
|
|
if ! getent group "$sftpAccessGroup" >/dev/null 2>&1; then
|
|
# printInfo "$systemLabel SFTP Create SFTP access group: $sftpAccessGroup"
|
|
run output error groupadd "$sftpAccessGroup" || { appError "SFTP Failed create group $sftpAccessGroup: $error"; return 1; }
|
|
fi
|
|
|
|
grep -Eq '^[[:space:]]*Subsystem[[:space:]]+sftp[[:space:]]+' "$sftpConfig" || {
|
|
appError "SFTP Not found Subsystem in $sftpConfig"
|
|
return 1
|
|
}
|
|
|
|
sftpBackup="$sftpConfig.$(date +%F_%H-%M-%S).bak"
|
|
cp -a "$sftpConfig" "$sftpBackup" || { appError "SFTP Backup failed"; return 1; }
|
|
|
|
# printInfo "$systemLabel SFTP Update config..."
|
|
if ! sed -i -E 's|^[[:space:]]*Subsystem[[:space:]]+sftp[[:space:]]+.*$|Subsystem sftp internal-sftp|' "$sftpConfig"; then
|
|
cp -a "$sftpBackup" "$sftpConfig"
|
|
appError "SFTP Update Subsystem SFTP failed"
|
|
return 1
|
|
fi
|
|
if ! sed -i \
|
|
-e '/^# --- KUBE SFTP GLOBAL BEGIN ---$/,/^# --- KUBE SFTP GLOBAL END ---$/d' \
|
|
-e '/^# --- KUBE SFTP GROUP BEGIN ---$/,/^# --- KUBE SFTP GROUP END ---$/d' \
|
|
"$sftpConfig"; then
|
|
cp -a "$sftpBackup" "$sftpConfig"
|
|
appError "SFTP Remove old SFTP blocks failed"
|
|
return 1
|
|
fi
|
|
local tmpFile
|
|
tmpFile="$(mktemp)"
|
|
if ! {
|
|
cat "$appAssetsPath/system/sftp-global.conf"
|
|
echo
|
|
cat "$sftpConfig"
|
|
echo
|
|
sed "s|{{sftp_access_group}}|$sftpAccessGroup|g" "$appAssetsPath/system/sftp-group.conf"
|
|
} > "$tmpFile" || ! mv "$tmpFile" "$sftpConfig"; then
|
|
rm -f "$tmpFile"
|
|
cp -a "$sftpBackup" "$sftpConfig"
|
|
appError "SFTP Append SFTP config blocks failed"
|
|
return 1
|
|
fi
|
|
|
|
# printInfo "$systemLabel SFTP Config validation..."
|
|
if ! run output error "$sshdBin" -t -f "$sftpConfig"; then
|
|
cp -a "$sftpBackup" "$sftpConfig"
|
|
appError "SFTP Config validation failed: $error"
|
|
return 1
|
|
fi
|
|
|
|
# printInfo "$systemLabel SFTP Reload/restart service..."
|
|
if ! run output error systemctl reload "$sshService"; then
|
|
if ! run output error systemctl restart "$sshService"; then
|
|
cp -a "$sftpBackup" "$sftpConfig"
|
|
systemctl restart "$sshService" >/dev/null 2>&1 || true
|
|
appError "SFTP Reload/restart failed: $error"
|
|
return 1
|
|
fi
|
|
fi
|
|
|
|
# printSuccess "$systemLabel SFTP Adding support complete"
|
|
}
|
|
|
|
function fail2banConfigUpdate() {
|
|
local sourceConfig targetConfig
|
|
targetConfig="/etc/fail2ban/jail.local"
|
|
sourceConfig="$appAssetsPath/system/fail2ban.conf"
|
|
|
|
[[ ! -f "$targetConfig" ]] || {
|
|
appError "The file $targetConfig already exists. Make changes manually.";
|
|
return 1;
|
|
}
|
|
|
|
cp -a "$sourceConfig" "$targetConfig" >/dev/null 2>&1 || {
|
|
appError "File copy error";
|
|
return 1;
|
|
}
|
|
|
|
if command -v fail2ban-client >/dev/null 2>&1; then
|
|
fail2ban-client -t >/dev/null 2>&1 || {
|
|
appError "Configuration error";
|
|
return 1;
|
|
}
|
|
fi
|
|
|
|
systemctl restart fail2ban
|
|
sleep 2
|
|
systemctl is-active --quiet fail2ban || {
|
|
appError "fail2ban did not start after applying the new configuration";
|
|
return 1;
|
|
}
|
|
}
|