#!/bin/bash
# PALI, The Perfect Automatic Lamp Installer, by Christophe Casalegno / Brain 0verride.
# Historical version 2.3 for Debian 9, restored 2026-10-26.
# Original archive: https://web.archive.org/web/20191113224438/https://www.christophe-casalegno.com/tools/paliv9.sh
# This separate revision targets a NEW, ISOLATED Debian 9 laboratory VM.
# Debian 9 and its application stack remain obsolete. This is not a supported
# production installer. No service installation was tested during restoration.
# All Debian packages still require APT authentication. Never bypass an APT refusal.
# Optional historical Internet installers are explicitly unavailable in this revision.

set -Eeuo pipefail
umask 077
export LC_ALL=C
export DEBIAN_FRONTEND=noninteractive
PATH=/usr/sbin:/usr/bin:/sbin:/bin
export PATH

pali_usage() {
    cat <<'USAGE'
PALI 2.3 archive restoration for Debian 9 (stretch), laboratory use only.
  bash paliv9.sh --check -a admin@example.net -n lab.example.net --keyring /path/archive-keys.gpg
  bash paliv9.sh --install-archive -a admin@example.net -n lab.example.net --keyring /path/archive-keys.gpg

--check             Read-only checks; no package download or machine modification.
--install-archive   Explicitly install on a NEW Debian 9 VM only.
--keyring PATH      A trusted, independently verified Debian archive keyring.
-a EMAIL           Administrator address; no credentials are sent by email.
-n FQDN            Hostname for the first site.
-s PORT            SSH port (otherwise preserve the current daemon port).
-l 0               HTTP only. Historical Certbot/ACME integration is unavailable.
-h, --help         This help; no root access required.

-j, -w and -l 1 fail before modification: NodeSource, Webmin and historical
Certbot cannot be restored by substituting unverified current Internet installers.
Composer, Grunt, Drush, WP-CLI and Drupal Console are also explicitly omitted.
MySQL binds to loopback; phpMyAdmin and Munin require local access/SSH forwarding.
FTP requires TLS for control and data, with a self-signed local certificate.
The script does not enable root SSH passwords or reset the Unix root password.
An APT refusal, including an expired archive signature, stops installation.
USAGE
}

pali_die() { printf 'PALI: %s\n' "$*" >&2; exit 1; }
pali_mode=check
pali_version=9
pali_codename=stretch
pali_script_dir=$(CDPATH= cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)
pali_keyring="$pali_script_dir/debian-archive-keyring.gpg"
admin_mail=''
myhost=''
ssh_port=''
pali_ssh_requested=0
letsencrypt=0

while (($#)); do
    case "$1" in
        -h|--help) pali_usage; exit 0 ;;
        --check) pali_mode=check; shift ;;
        --install-archive) pali_mode=install; shift ;;
        -a|-n|-s|--keyring|-l)
            (($# >= 2)) || pali_die "Missing argument for $1"
            case "$1" in
                -a) admin_mail=$2 ;;
                -n) myhost=$2 ;;
                -s) ssh_port=$2; pali_ssh_requested=1 ;;
                --keyring) pali_keyring=$2 ;;
                -l) [[ $2 == 0 ]] || pali_die 'Historical Certbot/ACME is not restored; use -l 0.' ;;
            esac
            shift 2 ;;
        -j) pali_die 'NodeSource/Grunt Internet installation is not restored; -j is unavailable.' ;;
        -w) pali_die 'Webmin and its historical third-party repository are not restored; -w is unavailable.' ;;
        *) pali_die "Unknown option: $1" ;;
    esac
done

pali_fqdn_valid() {
    local pali_name=$1 pali_label
    [[ ${#pali_name} -le 253 && $pali_name == *.* && $pali_name != *..* ]] || return 1
    [[ $pali_name =~ ^[a-zA-Z0-9][a-zA-Z0-9.-]*[a-zA-Z0-9]$ ]] || return 1
    local IFS=.
    for pali_label in $pali_name; do
        [[ ${#pali_label} -le 63 && $pali_label =~ ^[a-zA-Z0-9]([a-zA-Z0-9-]*[a-zA-Z0-9])?$ ]] || return 1
    done
}

pali_ipv4_valid() {
    local pali_addr=$1 pali_octet
    [[ $pali_addr =~ ^[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+$ ]] || return 1
    local IFS=.
    for pali_octet in $pali_addr; do
        [[ ${#pali_octet} -le 3 && $pali_octet == 0 || $pali_octet =~ ^[1-9][0-9]{0,2}$ ]] || return 1
        ((10#$pali_octet <= 255)) || return 1
    done
    [[ $pali_addr != 0.0.0.0 && $pali_addr != 127.* && $pali_addr != 255.255.255.255 ]]
}

pali_port_valid() { [[ $1 =~ ^[1-9][0-9]{0,4}$ ]] && ((10#$1 <= 65535)); }

[[ -n $admin_mail && $admin_mail =~ ^[a-zA-Z0-9._+-]+@[a-zA-Z0-9.-]+$ ]] || pali_die 'Supply a simple administrator email address with -a.'
pali_mail_local=${admin_mail%%@*}
[[ ${#admin_mail} -le 254 && ${#pali_mail_local} -le 64 ]] || pali_die 'Email address is too long.'
pali_fqdn_valid "${admin_mail#*@}" || pali_die 'Invalid email domain.'
[[ -n $myhost ]] || myhost=$(hostname -f 2>/dev/null || true)
pali_fqdn_valid "$myhost" || pali_die 'Supply a valid fully qualified hostname with -n.'
myhost=${myhost,,}
if [[ -n $ssh_port ]]; then pali_port_valid "$ssh_port" || pali_die 'SSH port must be an integer from 1 to 65535.'; fi
[[ -r /etc/os-release ]] || pali_die 'Cannot identify the operating system.'
pali_os_id=$(sed -n 's/^ID=//p' /etc/os-release | tr -d '"')
pali_os_version=$(sed -n 's/^VERSION_ID=//p' /etc/os-release | tr -d '"')
[[ $pali_os_id == debian && ${pali_os_version%%.*} == "$pali_version" ]] || pali_die "This variant requires Debian $pali_version; found $pali_os_id $pali_os_version."
[[ $(dpkg --print-architecture) == amd64 ]] || pali_die 'Only Debian amd64 archive package metadata was checked for this restoration.'
((EUID == 0)) || pali_die 'Run the target-machine checks/install as root.'
for pali_command in apt-get dpkg-query mktemp awk sed grep hostname sshd iptables ip6tables; do
    command -v "$pali_command" >/dev/null || pali_die "Missing base-system command: $pali_command"
done
iptables-save >/dev/null || pali_die 'IPv4 firewall support is required.'
ip6tables-save >/dev/null || pali_die 'IPv6 firewall support is required.'
[[ -f $pali_keyring && -r $pali_keyring && -s $pali_keyring ]] || pali_die "Supply a verified Debian archive keyring with --keyring (missing: $pali_keyring)."

ip=''
for pali_address in $(hostname -I); do
    if pali_ipv4_valid "$pali_address"; then ip=$pali_address; break; fi
done
[[ -n $ip ]] || pali_die 'No usable local IPv4 address was found.'
pali_previous_ssh_ports=$(sshd -T | awk '$1 == "port" {print $2}')
[[ -n $pali_previous_ssh_ports ]] || pali_die 'Cannot determine the current SSH port.'
for pali_port in $pali_previous_ssh_ports; do pali_port_valid "$pali_port" || pali_die 'Invalid current SSH configuration.'; done
[[ -n $ssh_port ]] || ssh_port=${pali_previous_ssh_ports%%$'\n'*}
[[ $ssh_port != 20 && $ssh_port != 21 && $ssh_port != 80 && $ssh_port != 443 && $ssh_port != 3306 && ! ($ssh_port -ge 30000 && $ssh_port -le 30100) ]] || pali_die 'The SSH port conflicts with a reserved service/passive FTP port.'

for pali_package in apache2 apache2-bin mysql-server mysql-server-5.5 mariadb-server mariadb-server-10.1 php5-fpm php7.0-fpm pure-ftpd munin postfix; do
    if [[ $(dpkg-query -W -f='${Status}' "$pali_package" 2>/dev/null || true) == 'install ok installed' ]]; then
        pali_die "Existing $pali_package installation detected. Use a NEW VM; this installer will not overwrite an existing stack."
    fi
done
memory=$(awk '/^MemTotal:/ {print int($2/2048)}' /proc/meminfo)
[[ $memory =~ ^[0-9]+$ && $memory -ge 1024 ]] || pali_die 'At least 2 GiB RAM is required for this historical stack.'
[[ ! -d /etc/apache2 || -z $(ls -A /etc/apache2) ]] || pali_die 'Existing Apache configuration detected; use a clean VM.'
[[ ! -e /root/pali-archives/"$myhost".credentials ]] || pali_die 'A previous credential record exists; inspect the previous attempt.'
[[ ! -e /root/"$myhost".installed ]] || pali_die 'A previous installation marker exists.'
[[ ! -e /usr/sbin/policy-rc.d ]] || pali_die 'An existing policy-rc.d must be preserved; use a clean VM.'
[[ ! -d /var/lib/mysql || -z $(ls -A /var/lib/mysql) ]] || pali_die 'Existing MySQL data directory detected.'
printf 'Target verified: Debian %s amd64, %s (%s), SSH %s.\n' "$pali_version" "$myhost" "$ip" "$ssh_port"
printf '%s\n' 'Unavailable optional integrations: Composer, NodeSource, Grunt, Drush, WP-CLI, Drupal Console, Webmin, Certbot/ACME.'
if [[ $pali_mode == check ]]; then
    printf '%s\n' 'Read-only checks completed. No packages downloaded and no settings modified.'
    exit 0
fi

pali_work=''
pali_policy_created=0
pali_firewall_changed=0
pali_complete=0
pali_services_started=0
pali_packages_started=0
pali_cleanup() {
    local pali_status=$?
    trap - EXIT
    if ((pali_policy_created)) && [[ -f /usr/sbin/policy-rc.d ]] && grep -qx '# PALI archive temporary service-start guard' /usr/sbin/policy-rc.d; then
        rm -- /usr/sbin/policy-rc.d
    fi
    if ((pali_status != 0 && (pali_services_started || pali_packages_started))); then
        for pali_service in apache2 pure-ftpd postfix munin-node memcached fail2ban mysql php7.0-fpm; do
            service "$pali_service" stop >/dev/null 2>&1 || true
        done
    fi
    if ((pali_status != 0 && pali_firewall_changed)); then
        iptables-restore < "$pali_work/iptables.before" || true
        ip6tables-restore < "$pali_work/ip6tables.before" || true
    fi
    if [[ -n $pali_work && -d $pali_work && $pali_work == /var/tmp/pali-archive.* ]]; then rm -rf -- "$pali_work"; fi
    if ((pali_status != 0)); then printf '%s\n' 'PALI stopped on error. Installation is incomplete; no success marker was written. Inspect the local VM before any retry.' >&2; fi
    exit "$pali_status"
}
trap pali_cleanup EXIT
trap 'printf "PALI: command failed at line %s.\n" "$LINENO" >&2' ERR
trap 'exit 130' INT
trap 'exit 143' TERM

# APT uses exclusive archive sources, lists, cache and keyring. The system sources
# and trust stores are unchanged. Check/download ALL packages before changing the host.
pali_work=$(mktemp -d /var/tmp/pali-archive.XXXXXXXX)
chmod 0755 "$pali_work"
mkdir -p "$pali_work/lists/partial" "$pali_work/archives/partial"
chmod 0755 "$pali_work/lists" "$pali_work/archives" "$pali_work/lists/partial" "$pali_work/archives/partial"
if id _apt >/dev/null 2>&1; then
    chown _apt "$pali_work/lists/partial" "$pali_work/archives/partial"
fi
cp -- "$pali_keyring" "$pali_work/archive-keys.gpg"
chmod 0644 "$pali_work/archive-keys.gpg"
cat > "$pali_work/sources.list" <<'SOURCES'
deb http://archive.debian.org/debian stretch main contrib non-free
deb http://archive.debian.org/debian-security stretch/updates main contrib non-free

SOURCES
chmod 0644 "$pali_work/sources.list"
pali_apt_options=(
    -o "Dir::Etc::sourcelist=$pali_work/sources.list"
    -o 'Dir::Etc::sourceparts=-'
    -o "Dir::Etc::trusted=$pali_work/archive-keys.gpg"
    -o 'Dir::Etc::trustedparts=-'
    -o "Dir::State::lists=$pali_work/lists"
    -o "Dir::Cache::archives=$pali_work/archives"
    -o 'APT::Get::AllowUnauthenticated=false'
    -o 'Acquire::AllowInsecureRepositories=false'
    -o 'Acquire::AllowDowngradeToInsecureRepositories=false'
    
)
pali_apt() { apt-get "${pali_apt_options[@]}" "$@"; }
packages='libwww-perl ntpdate apt-transport-https man vim emacs iotop htop mc apache2 apache2-doc imagemagick php7.0-zip php-ssh2 php7.0-common php7.0-cli php7.0-mysql php7.0-pgsql php-apcu php7.0-curl php7.0-gd php7.0-intl php-imagick php7.0-imap php7.0-mcrypt php-memcache php-memcached php7.0-pspell php7.0-recode php7.0-sqlite3 php7.0-tidy php7.0-xmlrpc php7.0-xsl php-soap php7.0-mbstring php7.0-fpm mysql-server mysql-client phpmyadmin munin munin-node postfix mailutils memcached git rsync pure-ftpd ftp curl strace python-setuptools python-dev gcc librsync-dev librsync1 python-cffi python-crypto python-cryptography python-ecdsa python-jwt python-lockfile python-ndg-httpsclient python-oauthlib python-openssl python-paramiko python-pkg-resources python-ply python-pyasn1 python-pycparser python-six python-urllib3 rkhunter chkrootkit fail2ban screen openssl ca-certificates iptables apache2-utils'
backport_packages=''
read -r -a pali_packages <<< "$packages"
read -r -a pali_backport_packages <<< "$backport_packages"
pali_apt update
pali_apt -y --download-only install "${pali_packages[@]}"
if ((${#pali_backport_packages[@]})); then pali_apt -y --download-only -t "$pali_codename-backports" install "${pali_backport_packages[@]}"; fi
printf '%s\n' 'APT authenticated and downloaded the requested archive packages. Starting the local installation.'

pali_secret() { od -An -N24 -tx1 /dev/urandom | tr -d ' \n'; }
usr=$(printf '%s' "$myhost" | tr -cd 'a-z0-9' | cut -c1-12)
tld=$(od -An -N2 -tx1 /dev/urandom | tr -d ' \n')
sysuser="$usr$tld"
sysuser_pass=$(pali_secret)
mysqlroot_pass=$(pali_secret)
mysql_pass=$(pali_secret)
phpmyadmin_pass=$(pali_secret)
for pali_password in "$sysuser_pass" "$mysqlroot_pass" "$mysql_pass" "$phpmyadmin_pass"; do
    [[ $pali_password =~ ^[0-9a-f]{48}$ ]] || pali_die 'Secure password generation failed.'
done
id "$sysuser" >/dev/null 2>&1 && pali_die 'Generated account already exists.'
install -d -m 0700 /root/pali-archives
pali_credentials="/root/pali-archives/$myhost.credentials"
[[ ! -e $pali_credentials ]] || pali_die 'A previous credential record exists; inspect the previous attempt.'
cat > "$pali_credentials" <<CREDENTIALS
INCOMPLETE installation, credentials saved before package configuration.
Host: $myhost
Unix/FTPS user: $sysuser
Unix/FTPS password: $sysuser_pass
MySQL application user/database: $sysuser
MySQL application password: $mysql_pass
MariaDB root: local Unix socket authentication preserved.
phpMyAdmin internal database password: $phpmyadmin_pass
Unix root password and SSH root authentication are preserved.
CREDENTIALS
chmod 0600 "$pali_credentials"
printf 'Generated credentials saved in %s; installation is still incomplete.\n' "$pali_credentials"
auto=debconf-set-selections
# MariaDB root keeps the native unix_socket authentication.
$auto <<< "postfix postfix/mailname string $myhost"
$auto <<< 'postfix postfix/main_mailer_type select Internet Site'
$auto <<< 'phpmyadmin phpmyadmin/dbconfig-install boolean true'
$auto <<< "phpmyadmin phpmyadmin/app-password-confirm password $phpmyadmin_pass"
$auto <<< "phpmyadmin phpmyadmin/mysql/admin-pass password"
$auto <<< "phpmyadmin phpmyadmin/mysql/app-pass password $phpmyadmin_pass"
$auto <<< 'phpmyadmin phpmyadmin/reconfigure-webserver multiselect apache2'
cat > /usr/sbin/policy-rc.d <<'POLICY'
#!/bin/sh
# PALI archive temporary service-start guard
exit 101
POLICY
chmod 0755 /usr/sbin/policy-rc.d
pali_policy_created=1
pali_core_packages=()
for pali_package in "${pali_packages[@]}"; do
    if [[ $pali_package != phpmyadmin ]]; then pali_core_packages+=("$pali_package"); fi
 done
pali_packages_started=1
pali_apt -y --no-download install "${pali_core_packages[@]}"
if ((${#pali_backport_packages[@]})); then pali_apt -y --no-download -t "$pali_codename-backports" install "${pali_backport_packages[@]}"; fi

# Historic component paths, kept for the matching Debian release.
replacer=sed
replacer_options=-i
sshd_conf=/etc/ssh/sshd_config
vhosts_conf=/etc/apache2/sites-available
apache_conf=/etc/apache2/apache2.conf
fpm_conf=/etc/php/7.0/fpm/pool.d
php_conf=/etc/php/7.0/fpm/php.ini
phpcli_conf=/etc/php/7.0/cli/php.ini
mysql_conf=/etc/mysql/my.cnf
munin_conf=/etc/munin/apache24.conf
munin_master_conf=/etc/munin/munin.conf
munin_node_conf=/etc/munin/munin-node.conf
munin_orig=/usr/share/munin/plugins
munin_dest=/etc/munin/plugins
rkhunter_conf=/etc/default/rkhunter
fail2ban_conf=/etc/fail2ban/jail.local
phpmyadmin_conf=/etc/phpmyadmin/apache.conf
event_conf=/etc/apache2/mods-available/mpm_event.conf

content_firewall="
*filter
:INPUT DROP [0:0]
:FORWARD DROP [0:0]
:OUTPUT DROP [0:0]
-A INPUT -m state --state RELATED,ESTABLISHED -j ACCEPT
-A INPUT -i lo -j ACCEPT
-A INPUT -p icmp -j ACCEPT
-A INPUT -p tcp -m tcp --dport $ssh_port -j ACCEPT
-A INPUT -p tcp -m tcp --dport 80 -j ACCEPT
-A INPUT -p tcp -m tcp --dport 443 -j ACCEPT
-A INPUT -p tcp --dport 20 -j ACCEPT
-A INPUT -p tcp --dport 21 -j ACCEPT
-A INPUT -p tcp --dport 30000:30100 -j ACCEPT
-A INPUT -m state --state ESTABLISHED,RELATED -j ACCEPT
-A OUTPUT -m state --state RELATED,ESTABLISHED -j ACCEPT
-A OUTPUT -o lo -j ACCEPT
-A OUTPUT -p icmp -j ACCEPT
-A OUTPUT -p tcp -m tcp --dport 53 -j ACCEPT
-A OUTPUT -p udp -m udp --dport 53 -j ACCEPT
-A OUTPUT -p tcp -m tcp --dport 80 -j ACCEPT
-A OUTPUT -p tcp -m tcp --dport 22 -j ACCEPT
-A OUTPUT -p udp -m udp --dport 123 -j ACCEPT
-A OUTPUT -p tcp -m tcp --dport 443 -j ACCEPT
-A OUTPUT -p tcp -m tcp --dport 25 -j ACCEPT
-A OUTPUT -p tcp -m tcp --dport 22 -j ACCEPT
-A OUTPUT -p tcp -m tcp --dport 65022 -j ACCEPT
$(for pali_port in $pali_previous_ssh_ports; do printf -- '-A INPUT -p tcp --dport %s -j ACCEPT\n' "$pali_port"; done)
COMMIT
"


content_firewall6="
*filter
:INPUT DROP [0:0]
:FORWARD DROP [0:0]
:OUTPUT DROP [0:0]
-A INPUT -m state --state RELATED,ESTABLISHED -j ACCEPT
-A INPUT -i lo -j ACCEPT
-A INPUT -p ipv6-icmp -j ACCEPT
-A INPUT -p tcp -m tcp --dport $ssh_port -j ACCEPT
-A INPUT -p tcp -m tcp --dport 80 -j ACCEPT
-A INPUT -p tcp -m tcp --dport 443 -j ACCEPT
-A INPUT -p tcp --dport 20 -j ACCEPT
-A INPUT -p tcp --dport 21 -j ACCEPT
-A INPUT -p tcp --dport 30000:30100 -j ACCEPT
-A INPUT -m state --state ESTABLISHED,RELATED -j ACCEPT
-A OUTPUT -m state --state RELATED,ESTABLISHED -j ACCEPT
-A OUTPUT -o lo -j ACCEPT
-A OUTPUT -p ipv6-icmp -j ACCEPT
-A OUTPUT -p tcp -m tcp --dport 53 -j ACCEPT
-A OUTPUT -p udp -m udp --dport 53 -j ACCEPT
-A OUTPUT -p tcp -m tcp --dport 80 -j ACCEPT
-A OUTPUT -p tcp -m tcp --dport 22 -j ACCEPT
-A OUTPUT -p udp -m udp --dport 123 -j ACCEPT
-A OUTPUT -p tcp -m tcp --dport 443 -j ACCEPT
-A OUTPUT -p tcp -m tcp --dport 25 -j ACCEPT
-A OUTPUT -p tcp -m tcp --dport 22 -j ACCEPT
-A OUTPUT -p tcp -m tcp --dport 65022 -j ACCEPT
$(for pali_port in $pali_previous_ssh_ports; do printf -- '-A INPUT -p tcp --dport %s -j ACCEPT\n' "$pali_port"; done)
COMMIT
"


content_munin='
Alias /munin /var/cache/munin/www
<Directory /var/cache/munin/www>
AuthUserfile /etc/munin/munin-htpasswd
AuthName "Munin Access"
AuthType Basic
<RequireAll>
Require local
Require valid-user
</RequireAll>
Options None
</Directory>

ScriptAlias /munin-cgi/munin-cgi-graph /usr/lib/munin/cgi/munin-cgi-graph
<Location /munin-cgi/munin-cgi-graph>
AuthUserfile /etc/munin/munin-htpasswd
AuthName "Munin Access"
AuthType Basic
<RequireAll>
Require local
Require valid-user
</RequireAll>

<IfModule mod_fcgid.c>
            SetHandler fcgid-script
        </IfModule>
        <IfModule !mod_fcgid.c>
            SetHandler cgi-script
        </IfModule>
</Location>
'



content_fail2ban="
[INCLUDES]
before = paths-debian.conf

[DEFAULT]

ignoreip = 127.0.0.1/8
ignorecommand =
bantime  = 600
findtime = 600
maxretry = 3
backend = auto
enabled = false
usedns = warn
logencoding = auto
filter = %(__name__)s
destemail = $admin_mail
sendername = Fail2Ban $myhost
sender = fail2ban@$myhost
mta = sendmail
protocol = tcp
chain = INPUT
port = 1:65535
fail2ban_agent = Fail2Ban/%(fail2ban_version)s
banaction = iptables-multiport
banaction_allports = iptables-allports
action_ = %(banaction)s[name=%(__name__)s, port=\"%(port)s\", protocol=\"%(protocol)s\", chain=\"%(chain)s\"]
action_mw = %(banaction)s[name=%(__name__)s, port=\"%(port)s\", protocol=\"%(protocol)s\", chain=\"%(chain)s\"]
              %(mta)s-whois[name=%(__name__)s, dest=\"%(destemail)s\", protocol=\"%(protocol)s\", chain=\"%(chain)s\", sendername=\"%(sendername)s\"]
action_mwl = %(banaction)s[name=%(__name__)s, port=\"%(port)s\", protocol=\"%(protocol)s\", chain=\"%(chain)s\"]
               %(mta)s-whois-lines[name=%(__name__)s, dest=\"%(destemail)s\", logpath=%(logpath)s, chain=\"%(chain)s\", sendername=\"%(sendername)s\"]
action = %(action_)s

[ssh]

enabled  = true
port     = $ssh_port
filter   = sshd
logpath  = /var/log/auth.log
maxretry = 6

[ssh-ddos]

enabled  = false
port     = $ssh_port
filter   = sshd-ddos
logpath  = /var/log/auth.log
maxretry = 6

[apache]

enabled  = true
port     = http,https
filter   = apache-auth
logpath  = /var/log/apache2/*error.log
maxretry = 6

[apache-multiport]

enabled   = true
port      = http,https
filter    = apache-auth
logpath   = /var/log/apache2/*error.log
maxretry  = 6

[apache-noscript]

enabled  = true
port     = http,https
filter   = apache-noscript
logpath  = /var/log/apache2/*error.log
maxretry = 6

[apache-overflows]

enabled  = true
port     = http,https
filter   = apache-overflows
logpath  = /var/log/apache2/*error.log
maxretry = 2

[apache-modsecurity]

enabled  = false
filter   = apache-modsecurity
port     = http,https
logpath  = /var/log/apache*/*error.log
maxretry = 2

[apache-nohome]

enabled  = false
filter   = apache-nohome
port     = http,https
logpath  = /var/log/apache*/*error.log
maxretry = 2

[pure-ftpd]

enabled  = true
port     = ftp,ftp-data,ftps,ftps-data
filter   = pure-ftpd
logpath  = /var/log/syslog
maxretry = 6

[postfix]

enabled  = false
port     = smtp,ssmtp,submission
filter   = postfix
logpath  = /var/log/mail.log

[recidive]

enabled  = false
filter   = recidive
logpath  = /var/log/fail2ban.log
action   = iptables-allports[name=recidive]
           sendmail-whois-lines[name=recidive, logpath=/var/log/fail2ban.log]
bantime  = 604800  ; 1 week
findtime = 86400   ; 1 day
maxretry = 5

[ssh-blocklist]

enabled  = false
filter   = sshd
action   = iptables[name=SSH, port=ssh, protocol=tcp]
           sendmail-whois[name=SSH, dest=\"%(destemail)s\", sender=\"%(sender)s\", sendername=\"%(sendername)s\"]
           blocklist_de[email=\"%(sender)s\", apikey=\"xxxxxx\", service=\"%(filter)s\"]
logpath  = /var/log/sshd.log
maxretry = 20
"


content_event='
<IfModule mpm_event_module>
  ServerLimit         256
  StartServers        2
  MaxClients          1024
  MinSpareThreads     25
  MaxSpareThreads     75
  ThreadsPerChild     40
  MaxRequestsPerChild 0
</IfModule>
'


content_firstvhost="
<VirtualHost $ip:80>
ServerAdmin webmaster@$myhost
DocumentRoot /home/$sysuser/www
ServerName $myhost
CustomLog /var/log/apache2/$myhost.log combined
ErrorLog /var/log/apache2/$myhost-error.log
ScriptAlias /cgi-bin/ /home/$sysuser/cgi-bin/

<FilesMatch \.php$>
SetHandler \"proxy:unix:/run/php/$myhost.sock|fcgi://localhost/\"
</FilesMatch>

        <Directory /home/$sysuser/www>
                Options -Indexes +FollowSymLinks +MultiViews
                AllowOverride All
                Order allow,deny
                allow from all
                Require all granted
        </Directory>

</VirtualHost>
"

content_fpm="
[$myhost]

listen = /run/php/$myhost.sock

listen.backlog = -1
 
listen.allowed_clients = 127.0.0.1

listen.owner = $sysuser
listen.group = www-data
listen.mode = 0660

user = $sysuser
group = $sysuser

pm = dynamic
pm.max_children = 15
pm.start_servers = 3
pm.min_spare_servers = 2
pm.max_spare_servers = 5
pm.max_requests = 0

pm.status_path = /phpfpm-status-$myhost
ping.path = /phpfpm-ping-$myhost
ping.response = pong
 
request_terminate_timeout = 0
request_slowlog_timeout = 0
 
slowlog = /var/log/php-fpm/$myhost-slow.log
chdir = /
catch_workers_output = no
"


content_fpm_www="
[www]

user = www-data
group = www-data

listen = /run/php/php7.0-fpm.sock
listen.backlog = -1
listen.allowed_clients = 127.0.0.1

listen.owner = www-data
listen.group = www-data
listen.mode = 0660

pm = dynamic
pm.max_children = 15
pm.start_servers = 6
pm.min_spare_servers = 3
pm.max_spare_servers = 9
pm.max_requests = 0

request_terminate_timeout = 0
request_slowlog_timeout = 0
 
slowlog = /var/log/php-fpm/www-slow.log
chdir = /
catch_workers_output = no
"


content_mycnf='
[client]
port            = 3306
socket          = /var/run/mysqld/mysqld.sock

[mysqld_safe]
socket          = /var/run/mysqld/mysqld.sock
nice            = 0

[mysqld]
user            = mysql
pid-file        = /var/run/mysqld/mysqld.pid
socket          = /var/run/mysqld/mysqld.sock
port            = 3306
basedir         = /usr
datadir         = /var/lib/mysql
tmpdir          = /tmp
lc-messages-dir = /usr/share/mysql
skip-external-locking
bind-address            = 127.0.0.1
key_buffer_size         = 256M
max_allowed_packet      = 64M
thread_stack            = 192K
thread_cache_size       = 256
myisam-recover         = BACKUP
query_cache_limit       = 1M
query_cache_size        = 128M
max_connections         = 128
connect_timeout         = 10
wait_timeout            = 600
table_cache = 4096
table_open_cache = 4096
table_definition_cache = 4096
max_heap_table_size = 512M
sort_buffer_size        = 32M
bulk_insert_buffer_size = 16M
tmp_table_size          = 512M


log_error = /var/log/mysql/error.log
expire_logs_days        = 10
max_binlog_size         = 100M

innodb_buffer_pool_size = 2048M
innodb_log_buffer_size  = 16M
innodb_file_per_table   = 1
innodb_open_files       = 400
innodb_io_capacity      = 400
innodb_read_io_threads=64
innodb_write_io_threads=64
innodb_thread_concurrency=16
innodb_flush_method     = O_DIRECT
innodb_flush_log_at_trx_commit=2

[mysqldump]
quick
quote-names
max_allowed_packet      = 16M

[mysql]

[isamchk]
key_buffer_size         = 16M

!includedir /etc/mysql/conf.d/
'


content_genmysql="
CREATE USER '$sysuser'@'localhost' IDENTIFIED BY '$mysql_pass';
CREATE DATABASE IF NOT EXISTS \`"$sysuser"\` ;
GRANT ALL PRIVILEGES ON \`"$sysuser"\`.* TO '$sysuser'@'localhost';
FLUSH PRIVILEGES;
"


# Hostname changes do not rewrite SSH public keys or unrelated host entries.
cuthost=${myhost%%.*}
printf '%s\n' "$cuthost" > /etc/hostname
hostname "$cuthost"
if ! awk -v host="$myhost" '{for(i=2;i<=NF;i++)if($i==host)found=1}END{exit !found}' /etc/hosts; then
    printf '%s %s %s\n' "$ip" "$myhost" "$cuthost" >> /etc/hosts
fi

install -d -m 0755 /etc/skel/www /etc/skel/cgi-bin /var/log/php-fpm
install -d -m 0700 /etc/skel/protected /root/pali-archives
useradd "$sysuser" --shell /bin/bash -m -U -d "/home/$sysuser"
printf '%s:%s\n' "$sysuser" "$sysuser_pass" | chpasswd
chmod 0751 "/home/$sysuser"
chmod 0755 "/home/$sysuser/www" "/home/$sysuser/cgi-bin"
chmod 0700 "/home/$sysuser/protected"

# Preserve root authentication policy and the root password. Only change the port
# if explicitly requested; keep previous SSH ports allowed by the firewall below.
if ((pali_ssh_requested)); then
cp -- "$sshd_conf" "$pali_work/sshd_config.before"
awk '!/^[[:space:]]*Port[[:space:]]/ {print}' "$sshd_conf" > "$pali_work/sshd_config"
{ printf 'Port %s\n' "$ssh_port"; cat "$pali_work/sshd_config"; } > "$sshd_conf"
chmod 0600 "$sshd_conf"
if ! sshd -t; then cp -- "$pali_work/sshd_config.before" "$sshd_conf"; pali_die 'SSH configuration validation failed; original file restored.'; fi
fi

sed -i "s#MAIL-ON-WARNING=.*#MAIL-ON-WARNING=$admin_mail#" "$rkhunter_conf"
sed -i 's#APT_AUTOGEN="false"#APT_AUTOGEN="true"#g' "$rkhunter_conf"
printf '%s\n' "$content_fail2ban" > "$fail2ban_conf"
chmod 0644 "$fail2ban_conf"

# FPM must be the active PHP handler; do not leave the mod_php prefork module active.
if a2query -m php7.0 >/dev/null 2>&1; then a2dismod php7.0; fi
if a2query -m mpm_prefork >/dev/null 2>&1; then a2dismod mpm_prefork; fi
a2enmod mpm_event userdir cgid rewrite ssl expires deflate headers actions proxy_fcgi setenvif
a2enconf php7.0-fpm
sed -i 's#Timeout 300#Timeout 30#; s#KeepAlive On#KeepAlive Off#' "$apache_conf"
printf '%s\n' "$content_event" > "$event_conf"
printf '%s\n' "$content_firstvhost" > "$vhosts_conf/$myhost.conf"
printf '%s\n' "$content_fpm" > "$fpm_conf/$myhost.conf"
printf '%s\n' "$content_fpm_www" > "$fpm_conf/www.conf"
chmod 0644 "$fpm_conf/www.conf"
chmod 0644 "$vhosts_conf/$myhost.conf" "$fpm_conf/$myhost.conf" "$event_conf"
a2ensite "$myhost.conf"
if a2query -s 000-default >/dev/null 2>&1; then a2dissite 000-default; fi

# These tuning settings are historical; adjust sizing for the isolated VM.
for pali_php_ini in "$php_conf" "$phpcli_conf"; do
    sed -i \
      -e 's#memory_limit = 128M#memory_limit = 512M#' \
      -e 's#;upload_tmp_dir =#upload_tmp_dir =/tmp#' \
      -e 's#upload_max_filesize = 2M#upload_max_filesize = 128M#' \
      -e 's#post_max_size = 8M#post_max_size = 128M#' \
      -e 's#max_file_uploads = 20#max_file_uploads = 128#' \
      -e 's#max_execution_time = 30#max_execution_time = 600#' \
      -e 's#max_input_time = 60#max_input_time = 600#' \
      -e 's#; max_input_vars = 1000#max_input_vars = 100000#' \
      -e 's#;max_input_vars = 1000#max_input_vars = 100000#' "$pali_php_ini"
done
# Preserve historical Memcached session storage for the original PHP contexts.
for pali_session_ini in "$phpcli_conf"; do
    sed -i -e 's#^session.save_handler = files#session.save_handler = memcached#' -e '/^;\{0,1\}session.save_path[[:space:]]*=/c\session.save_path = "127.0.0.1:11211"' "$pali_session_ini"
done

# Session/password data never traverse cleartext FTP. TLS=3 requires encryption
# on control AND data channels in Jessie/Stretch pure-ftpd.
printf 'yes\n' > /etc/pure-ftpd/conf/UnixAuthentication
printf 'no\n' > /etc/pure-ftpd/conf/PAMAuthentication
printf '1000\n' > /etc/pure-ftpd/conf/MinUID
printf 'yes\n' > /etc/pure-ftpd/conf/ChrootEveryone
printf 'yes\n' > /etc/pure-ftpd/conf/NoAnonymous
printf '3\n' > /etc/pure-ftpd/conf/TLS
printf '30000 30100\n' > /etc/pure-ftpd/conf/PassivePortRange
openssl req -x509 -nodes -days 365 -newkey rsa:4096 -sha256 \
    -keyout /etc/ssl/private/pure-ftpd.pem -out /etc/ssl/private/pure-ftpd.pem \
    -subj "/CN=$myhost"
chmod 0600 /etc/ssl/private/pure-ftpd.pem
chmod 0644 /etc/pure-ftpd/conf/{UnixAuthentication,PAMAuthentication,MinUID,ChrootEveryone,NoAnonymous,TLS,PassivePortRange}

# Restrict admin web interfaces to loopback, including when no HTTPS was configured.
printf '%s\n' "$content_munin" > "$munin_conf"
chmod 0644 "$munin_conf"
sed -i "s/#host_name localhost.localdomain/host_name $myhost/" "$munin_node_conf"
sed -i "s#localhost.localdomain#$myhost#" "$munin_master_conf"
for pali_plugin in mysql_ mysql_bytes mysql_innodb mysql_isam_space_ mysql_queries mysql_slowqueries mysql_threads postfix_mailstats; do
    if [[ -f $munin_orig/$pali_plugin ]]; then ln -sfn -- "$munin_orig/$pali_plugin" "$munin_dest/$pali_plugin"; fi
done
printf '%s\n' "$sysuser_pass" | htpasswd -i -c /etc/munin/munin-htpasswd "$sysuser"
chown root:www-data /etc/munin/munin-htpasswd
chmod 0640 /etc/munin/munin-htpasswd
printf '%s\n' "$content_mycnf" > "$mysql_conf"
sed -i "s#innodb_buffer_pool_size = 2048M#innodb_buffer_pool_size=${memory}M#" "$mysql_conf"
chmod 0644 "$mysql_conf"
# Configure phpMyAdmin only after its local SQL server is available.
pali_services_started=1
service mysql start
pali_apt -y --no-download install phpmyadmin
cat >> "$phpmyadmin_conf" <<'PHPMYADMIN'

# PALI archive: access via local SSH forwarding only.
<Directory /usr/share/phpmyadmin>
    Require local
</Directory>
PHPMYADMIN
cat >> "$phpmyadmin_conf" <<'ADMINFPM'
<Directory /usr/share/phpmyadmin>
  <FilesMatch \.php$>
    SetHandler "proxy:unix:/run/php/php7.0-fpm.sock|fcgi://localhost/"
  </FilesMatch>
</Directory>
ADMINFPM
chmod 0644 "$phpmyadmin_conf"

if grep -q '^-l ' /etc/memcached.conf; then
    sed -i 's/^-l .*/-l 127.0.0.1/' /etc/memcached.conf
else
    printf '\n-l 127.0.0.1\n' >> /etc/memcached.conf
fi
printf '<?php echo "test ok";?>\n' > "/home/$sysuser/www/index.php"
chmod 0644 "/home/$sysuser/www/index.php"
chown -R "$sysuser:$sysuser" "/home/$sysuser"

# Validate generated configurations before starting services or changing firewalls.
apache2ctl configtest
php7.0-fpm -t
fail2ban-client -d >/dev/null
sshd -t
iptables-save > "$pali_work/iptables.before"
ip6tables-save > "$pali_work/ip6tables.before"
pali_firewall_changed=1
printf '%s\n' "$content_firewall" > "$pali_work/iptables.rules"
printf '%s\n' "$content_firewall6" > "$pali_work/ip6tables.rules"
iptables-restore < "$pali_work/iptables.rules"
ip6tables-restore < "$pali_work/ip6tables.rules"

# Start services only after both families are filtered. MySQL remains loopback-only.
rm -- /usr/sbin/policy-rc.d
pali_policy_created=0
# Root uses the existing local MariaDB unix_socket authentication.
printf '%s\n' "$content_genmysql" > "$pali_work/site.sql"
chmod 0600 "$pali_work/site.sql"
mysql --protocol=socket --user=root < "$pali_work/site.sql"

service php7.0-fpm restart
service apache2 restart
if ((pali_ssh_requested)); then service ssh restart; fi
service pure-ftpd restart
service memcached restart
service munin-node restart
service postfix restart
service fail2ban restart
for pali_service in mysql php7.0-fpm apache2 ssh pure-ftpd memcached munin-node postfix fail2ban; do
    service "$pali_service" status >/dev/null || pali_die "Service failed: $pali_service"
done

# Persist the already validated IPv4/IPv6 rules. No obsolete temporary port-22 rule.
install -m 0600 "$pali_work/iptables.rules" /etc/pali-iptables.rules
install -m 0600 "$pali_work/ip6tables.rules" /etc/pali-ip6tables.rules
cat > /etc/network/if-pre-up.d/pali-firewall <<'FIREWALL'
#!/bin/sh
set -e
/sbin/iptables-restore < /etc/pali-iptables.rules
/sbin/ip6tables-restore < /etc/pali-ip6tables.rules
FIREWALL
chmod 0755 /etc/network/if-pre-up.d/pali-firewall
cat > /etc/cron.d/pali-clock <<'CRON'
0 1 * * * root /usr/sbin/ntpdate fr.pool.ntp.org
CRON
chmod 0644 /etc/cron.d/pali-clock

# All generated secrets remain in one root-only local file; no email or argv copies.
pali_credentials="/root/pali-archives/$myhost.credentials"
cat > "$pali_credentials" <<CREDENTIALS
PALI 2.3 archive restoration on $(date -u +%FT%TZ)
Host: $myhost
IPv4: $ip
SSH port: $ssh_port (original SSH firewall ports also retained: $pali_previous_ssh_ports)
Unix/FTPS user: $sysuser
Unix/FTPS password: $sysuser_pass
MySQL local database/user: $sysuser
MySQL application password: $mysql_pass
MariaDB root: local Unix socket authentication preserved.
phpMyAdmin internal database password: $phpmyadmin_pass
Munin local HTTP Basic credentials: $sysuser / $sysuser_pass
Unix root password and SSH root authentication were preserved.
FTPS uses a self-signed certificate; verify its fingerprint locally.
No credentials were emailed. Admin HTTP endpoints require loopback/SSH forwarding.
CREDENTIALS
chmod 0600 "$pali_credentials"
printf '%s\n' 'PALI archive restoration completed locally.' > "/root/$myhost.installed"
chmod 0600 "/root/$myhost.installed"
pali_complete=1
printf 'Installation finished. Credentials: %s (root only).\n' "$pali_credentials"
printf '%s\n' 'This remains an obsolete Debian laboratory stack, not a production security certification.'
exit 0
