# 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 "$vhostsPath/$domain/www" ]] || { appError "Vhost www directory does not exist: $vhostsPath/$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" }