0 - readme.md
· 553 B · Markdown
Raw
Initial Setup:
curl -sL https://beanman.net/gist/beanman109/alpine/raw/HEAD/initial-setup.sh | sh
or
wget -qO- https://beanman.net/gist/beanman109/alpine/raw/HEAD/initial-setup.sh | sh
Fail2ban Setup:
curl -sL https://beanman.net/gist/beanman109/alpine/raw/HEAD/fail2ban-setup.sh | sh
or
wget -qO- https://beanman.net/gist/beanman109/alpine/raw/HEAD/fail2ban-setup.sh | sh
Squid Setup:
curl -sL https://beanman.net/gist/beanman109/alpine/raw/HEAD/squid-setup.sh | sh
or
wget -qO- https://beanman.net/gist/beanman109/alpine/raw/HEAD/squid-setup.sh | sh
Initial Setup: curl -sL https://beanman.net/gist/beanman109/alpine/raw/HEAD/initial-setup.sh | sh or wget -qO- https://beanman.net/gist/beanman109/alpine/raw/HEAD/initial-setup.sh | sh
Fail2ban Setup: curl -sL https://beanman.net/gist/beanman109/alpine/raw/HEAD/fail2ban-setup.sh | sh or wget -qO- https://beanman.net/gist/beanman109/alpine/raw/HEAD/fail2ban-setup.sh | sh
Squid Setup: curl -sL https://beanman.net/gist/beanman109/alpine/raw/HEAD/squid-setup.sh | sh or wget -qO- https://beanman.net/gist/beanman109/alpine/raw/HEAD/squid-setup.sh | sh
fail2ban-setup.sh
· 1.8 KiB · Bash
Raw
#!/bin/sh
# Ensure the script is run as root
if [ "$(id -u)" -ne 0 ]; then
echo "Error: This script must be run as root." >&2
exit 1
fi
echo "--- 1. Installing Fail2ban, Rsyslog, and Iptables ---"
apk update
# iptables is required for Fail2ban's default banning actions
apk add fail2ban rsyslog iptables
echo "--- 2. Ripping out BusyBox Syslog and configuring Rsyslog ---"
# Stop and remove the default Alpine logger
rc-service syslog stop 2>/dev/null
rc-update del syslog boot 2>/dev/null
# Enable Rsyslog
rc-update add rsyslog boot
# Force Rsyslog to use the traditional date format (must be at the absolute top of the config)
sed -i '/\$ActionFileDefaultTemplate RSYSLOG_TraditionalFileFormat/d' /etc/rsyslog.conf
echo '$ActionFileDefaultTemplate RSYSLOG_TraditionalFileFormat' > /tmp/rsyslog-new.conf
cat /etc/rsyslog.conf >> /tmp/rsyslog-new.conf
mv /tmp/rsyslog-new.conf /etc/rsyslog.conf
# Start Rsyslog
rc-service rsyslog restart
echo "--- 3. Creating Custom SSHD Filter ---"
mkdir -p /etc/fail2ban/filter.d
# This regex ignores Alpine/rsyslog prefix bloat and hunts directly for OpenSSH 9.8+ session failures
cat << 'EOF' > /etc/fail2ban/filter.d/sshd-nuclear.conf
[Definition]
failregex = ^.*(?:sshd|sshd-session)(?:\[\d+\])?: (?:Failed password for|Invalid user) .*? from <HOST>.*$
ignoreregex =
EOF
echo "--- 4. Configuring jail.local ---"
cat << 'EOF' > /etc/fail2ban/jail.local
[DEFAULT]
bantime = 1h
findtime = 10m
maxretry = 5
[sshd]
enabled = true
port = ssh
filter = sshd-nuclear
logpath = /var/log/messages
# Force polling to prevent silent failures on Alpine
backend = polling
EOF
echo "--- 5. Enabling and Starting Fail2ban ---"
rc-update add fail2ban default
rc-service fail2ban restart
echo ""
echo "Fail2ban install complete. Check Fail2ban is now actively monitoring /var/log/messages."
| 1 | #!/bin/sh |
| 2 | |
| 3 | # Ensure the script is run as root |
| 4 | if [ "$(id -u)" -ne 0 ]; then |
| 5 | echo "Error: This script must be run as root." >&2 |
| 6 | exit 1 |
| 7 | fi |
| 8 | |
| 9 | echo "--- 1. Installing Fail2ban, Rsyslog, and Iptables ---" |
| 10 | apk update |
| 11 | # iptables is required for Fail2ban's default banning actions |
| 12 | apk add fail2ban rsyslog iptables |
| 13 | |
| 14 | echo "--- 2. Ripping out BusyBox Syslog and configuring Rsyslog ---" |
| 15 | # Stop and remove the default Alpine logger |
| 16 | rc-service syslog stop 2>/dev/null |
| 17 | rc-update del syslog boot 2>/dev/null |
| 18 | |
| 19 | # Enable Rsyslog |
| 20 | rc-update add rsyslog boot |
| 21 | |
| 22 | # Force Rsyslog to use the traditional date format (must be at the absolute top of the config) |
| 23 | sed -i '/\$ActionFileDefaultTemplate RSYSLOG_TraditionalFileFormat/d' /etc/rsyslog.conf |
| 24 | echo '$ActionFileDefaultTemplate RSYSLOG_TraditionalFileFormat' > /tmp/rsyslog-new.conf |
| 25 | cat /etc/rsyslog.conf >> /tmp/rsyslog-new.conf |
| 26 | mv /tmp/rsyslog-new.conf /etc/rsyslog.conf |
| 27 | |
| 28 | # Start Rsyslog |
| 29 | rc-service rsyslog restart |
| 30 | |
| 31 | echo "--- 3. Creating Custom SSHD Filter ---" |
| 32 | mkdir -p /etc/fail2ban/filter.d |
| 33 | # This regex ignores Alpine/rsyslog prefix bloat and hunts directly for OpenSSH 9.8+ session failures |
| 34 | cat << 'EOF' > /etc/fail2ban/filter.d/sshd-nuclear.conf |
| 35 | [Definition] |
| 36 | failregex = ^.*(?:sshd|sshd-session)(?:\[\d+\])?: (?:Failed password for|Invalid user) .*? from <HOST>.*$ |
| 37 | ignoreregex = |
| 38 | EOF |
| 39 | |
| 40 | echo "--- 4. Configuring jail.local ---" |
| 41 | cat << 'EOF' > /etc/fail2ban/jail.local |
| 42 | [DEFAULT] |
| 43 | bantime = 1h |
| 44 | findtime = 10m |
| 45 | maxretry = 5 |
| 46 | |
| 47 | [sshd] |
| 48 | enabled = true |
| 49 | port = ssh |
| 50 | filter = sshd-nuclear |
| 51 | logpath = /var/log/messages |
| 52 | # Force polling to prevent silent failures on Alpine |
| 53 | backend = polling |
| 54 | EOF |
| 55 | |
| 56 | echo "--- 5. Enabling and Starting Fail2ban ---" |
| 57 | rc-update add fail2ban default |
| 58 | rc-service fail2ban restart |
| 59 | |
| 60 | echo "" |
| 61 | echo "Fail2ban install complete. Check Fail2ban is now actively monitoring /var/log/messages." |
initial-setup.sh
· 2.5 KiB · Bash
Raw
#!/bin/sh
# Ensure the script is run as root
if [ "$(id -u)" -ne 0 ]; then
echo "Error: This script must be run as root."
exit 1
fi
echo "--- Starting Alpine Linux Initial Setup ---"
# 1. Point to latest-stable and enable the community repository
echo "-> Configuring APK repositories..."
sed -i 's/v[0-9]\.[0-9]*/latest-stable/g' /etc/apk/repositories
sed -i '/community/s/^#//' /etc/apk/repositories
# 2. Update and upgrade the system
echo "-> Updating and upgrading system packages..."
apk update
apk upgrade -a
# 3. Install requested and essential utilities
echo "-> Installing utilities, security tools, and rsyslog..."
apk add btop chrony tzdata fail2ban openssh iptables curl nano rsyslog
# 4. Configure Timezone
echo "-> Configuring timezone (Australia/Brisbane)..."
setup-timezone -z Australia/Brisbane
# 5. Configure and enable Chrony (NTP)
echo "-> Enabling and starting Chrony..."
rc-update add chronyd default
rc-service chronyd restart
# 6. Configure and enable OpenSSH
echo "-> Enabling and starting SSHD..."
rc-update add sshd default
rc-service sshd restart
# 7. Configure Logging (Rsyslog overriding Busybox)
echo "-> Swapping default syslog for rsyslog with traditional formatting..."
rc-service syslog stop 2>/dev/null
rc-update del syslog boot 2>/dev/null
rc-update add rsyslog boot
# Force Rsyslog to use the traditional date format required by Fail2ban
sed -i '/\$ActionFileDefaultTemplate RSYSLOG_TraditionalFileFormat/d' /etc/rsyslog.conf
echo '$ActionFileDefaultTemplate RSYSLOG_TraditionalFileFormat' > /tmp/rsyslog-new.conf
cat /etc/rsyslog.conf >> /tmp/rsyslog-new.conf
mv /tmp/rsyslog-new.conf /etc/rsyslog.conf
rc-service rsyslog restart
# 8. Configure Fail2Ban for OpenSSH
echo "-> Configuring Fail2Ban for SSH..."
# Create the custom brute-force SSH filter
mkdir -p /etc/fail2ban/filter.d
cat << 'EOF' > /etc/fail2ban/filter.d/sshd-nuclear.conf
[Definition]
failregex = ^.*(?:sshd|sshd-session)(?:\[\d+\])?: (?:Failed password for|Invalid user) .*? from <HOST>.*$
ignoreregex =
EOF
# Create the local jail configuration
cat << 'EOF' > /etc/fail2ban/jail.d/sshd.local
[sshd]
enabled = true
port = ssh
filter = sshd-nuclear
logpath = /var/log/messages
# Force polling so Alpine doesn't silently fail file watches
backend = polling
maxretry = 5
bantime = 3600
findtime = 600
EOF
# Enable and start Fail2Ban
rc-update add fail2ban default
rc-service fail2ban restart
echo "--- Alpine Linux Initial Setup Complete! ---"
echo "It is highly recommended to reboot the system to ensure all kernel and package upgrades take effect."
| 1 | #!/bin/sh |
| 2 | |
| 3 | # Ensure the script is run as root |
| 4 | if [ "$(id -u)" -ne 0 ]; then |
| 5 | echo "Error: This script must be run as root." |
| 6 | exit 1 |
| 7 | fi |
| 8 | |
| 9 | echo "--- Starting Alpine Linux Initial Setup ---" |
| 10 | |
| 11 | # 1. Point to latest-stable and enable the community repository |
| 12 | echo "-> Configuring APK repositories..." |
| 13 | sed -i 's/v[0-9]\.[0-9]*/latest-stable/g' /etc/apk/repositories |
| 14 | sed -i '/community/s/^#//' /etc/apk/repositories |
| 15 | |
| 16 | # 2. Update and upgrade the system |
| 17 | echo "-> Updating and upgrading system packages..." |
| 18 | apk update |
| 19 | apk upgrade -a |
| 20 | |
| 21 | # 3. Install requested and essential utilities |
| 22 | echo "-> Installing utilities, security tools, and rsyslog..." |
| 23 | apk add btop chrony tzdata fail2ban openssh iptables curl nano rsyslog |
| 24 | |
| 25 | # 4. Configure Timezone |
| 26 | echo "-> Configuring timezone (Australia/Brisbane)..." |
| 27 | setup-timezone -z Australia/Brisbane |
| 28 | |
| 29 | # 5. Configure and enable Chrony (NTP) |
| 30 | echo "-> Enabling and starting Chrony..." |
| 31 | rc-update add chronyd default |
| 32 | rc-service chronyd restart |
| 33 | |
| 34 | # 6. Configure and enable OpenSSH |
| 35 | echo "-> Enabling and starting SSHD..." |
| 36 | rc-update add sshd default |
| 37 | rc-service sshd restart |
| 38 | |
| 39 | # 7. Configure Logging (Rsyslog overriding Busybox) |
| 40 | echo "-> Swapping default syslog for rsyslog with traditional formatting..." |
| 41 | rc-service syslog stop 2>/dev/null |
| 42 | rc-update del syslog boot 2>/dev/null |
| 43 | rc-update add rsyslog boot |
| 44 | |
| 45 | # Force Rsyslog to use the traditional date format required by Fail2ban |
| 46 | sed -i '/\$ActionFileDefaultTemplate RSYSLOG_TraditionalFileFormat/d' /etc/rsyslog.conf |
| 47 | echo '$ActionFileDefaultTemplate RSYSLOG_TraditionalFileFormat' > /tmp/rsyslog-new.conf |
| 48 | cat /etc/rsyslog.conf >> /tmp/rsyslog-new.conf |
| 49 | mv /tmp/rsyslog-new.conf /etc/rsyslog.conf |
| 50 | |
| 51 | rc-service rsyslog restart |
| 52 | |
| 53 | # 8. Configure Fail2Ban for OpenSSH |
| 54 | echo "-> Configuring Fail2Ban for SSH..." |
| 55 | |
| 56 | # Create the custom brute-force SSH filter |
| 57 | mkdir -p /etc/fail2ban/filter.d |
| 58 | cat << 'EOF' > /etc/fail2ban/filter.d/sshd-nuclear.conf |
| 59 | [Definition] |
| 60 | failregex = ^.*(?:sshd|sshd-session)(?:\[\d+\])?: (?:Failed password for|Invalid user) .*? from <HOST>.*$ |
| 61 | ignoreregex = |
| 62 | EOF |
| 63 | |
| 64 | # Create the local jail configuration |
| 65 | cat << 'EOF' > /etc/fail2ban/jail.d/sshd.local |
| 66 | [sshd] |
| 67 | enabled = true |
| 68 | port = ssh |
| 69 | filter = sshd-nuclear |
| 70 | logpath = /var/log/messages |
| 71 | # Force polling so Alpine doesn't silently fail file watches |
| 72 | backend = polling |
| 73 | maxretry = 5 |
| 74 | bantime = 3600 |
| 75 | findtime = 600 |
| 76 | EOF |
| 77 | |
| 78 | # Enable and start Fail2Ban |
| 79 | rc-update add fail2ban default |
| 80 | rc-service fail2ban restart |
| 81 | |
| 82 | echo "--- Alpine Linux Initial Setup Complete! ---" |
| 83 | echo "It is highly recommended to reboot the system to ensure all kernel and package upgrades take effect." |
squid-setup.sh
· 15 KiB · Bash
Raw
#!/bin/sh
set -eu
# ============================================================
# Interactive Squid HTTPS Proxy Installer with Let's Encrypt
# Alpine Linux Edition (OpenRC)
#
# Creates:
# Firefox/FoxyProxy -> HTTPS proxy -> Squid -> Internet
#
# IMPORTANT:
# In FoxyProxy, use the DOMAIN NAME, not the IP address.
# ============================================================
RED="\033[31m"
GREEN="\033[32m"
YELLOW="\033[33m"
BLUE="\033[34m"
RESET="\033[0m"
log() { printf "${GREEN}[OK]${RESET} %s\n" "$*"; }
info() { printf "${BLUE}[INFO]${RESET} %s\n" "$*"; }
warn() { printf "${YELLOW}[WARN]${RESET} %s\n" "$*"; }
die() { printf "${RED}[ERROR]${RESET} %s\n" "$*" >&2; exit 1; }
SQUID_USER="squid"
SQUID_GROUP="squid"
STOPPED_SERVICES=""
require_root() {
if [ "$(id -u)" -ne 0 ]; then
die "Run this script as root."
fi
}
ask() {
local prompt="$1"
local default="${2:-}"
local value
# Output the prompt to stderr (>&2) so it displays on the screen
# even when the function's output is being captured by a variable.
if [ -n "$default" ]; then
printf "%s [%s]: " "$prompt" "$default" >&2
else
printf "%s: " "$prompt" >&2
fi
read -r value
echo "${value:-$default}"
}
ask_password_confirmed() {
local pass1 pass2
while true; do
printf "Proxy password: " >&2
stty -echo
read -r pass1
stty echo
printf '\n' >&2
printf "Confirm proxy password: " >&2
stty -echo
read -r pass2
stty echo
printf '\n' >&2
if [ -z "$pass1" ]; then
warn "Password cannot be empty." >&2
continue
fi
if [ "$pass1" != "$pass2" ]; then
warn "Passwords do not match." >&2
continue
fi
printf '%s' "$pass1"
return 0
done
}
validate_domain() {
local domain="$1"
if ! echo "$domain" | grep -qE '^[A-Za-z0-9.-]+\.[A-Za-z]{2,}$'; then
die "Invalid domain: $domain"
fi
if echo "$domain" | grep -qE '^[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+$'; then
die "Do not use an IP address. Use a real hostname, e.g. proxy.example.com"
fi
}
validate_port() {
local port="$1"
if ! echo "$port" | grep -qE '^[0-9]+$'; then
die "Invalid port: $port"
fi
if [ "$port" -lt 1 ] || [ "$port" -gt 65535 ]; then
die "Port must be between 1 and 65535."
fi
}
detect_proxy_user_group() {
if id squid >/dev/null 2>&1; then
SQUID_USER="squid"
elif id proxy >/dev/null 2>&1; then
SQUID_USER="proxy"
else
SQUID_USER="squid"
fi
if getent group squid >/dev/null 2>&1; then
SQUID_GROUP="squid"
elif getent group proxy >/dev/null 2>&1; then
SQUID_GROUP="proxy"
else
SQUID_GROUP="$SQUID_USER"
fi
}
install_packages() {
info "Installing required packages via apk..."
apk update
apk add squid apache2-utils certbot openssl curl ca-certificates bind-tools iptables
log "Packages installed."
}
check_squid_openssl() {
info "Checking Squid OpenSSL support..."
if squid -v 2>/dev/null | grep -qi openssl; then
log "Squid appears to have OpenSSL support."
else
warn "Could not confirm OpenSSL support from squid -v."
warn "Alpine's default squid should support it, but check logs if https_port fails."
fi
}
check_dns() {
local domain="$1"
info "Checking DNS for $domain..."
local resolved_v4
local resolved_v6
resolved_v4="$(dig +short A "$domain" | tail -n1 || true)"
resolved_v6="$(dig +short AAAA "$domain" | tail -n1 || true)"
if [ -z "$resolved_v4" ] && [ -z "$resolved_v6" ]; then
warn "No A or AAAA record found for $domain."
warn "Let's Encrypt will fail unless this hostname points to this server."
printf "Continue anyway? [y/N]: "
read -r yn
if [ "$yn" != "y" ] && [ "$yn" != "Y" ]; then
die "Stopped."
fi
else
if [ -n "$resolved_v4" ]; then
log "$domain A record: $resolved_v4"
fi
if [ -n "$resolved_v6" ]; then
log "$domain AAAA record: $resolved_v6"
else
warn "$domain has no AAAA record. That is fine if you only want IPv4."
fi
fi
return 0
}
open_firewall() {
local port="$1"
info "Attempting to open firewall ports 80 and $port..."
if command -v iptables >/dev/null 2>&1; then
iptables -C INPUT -p tcp --dport 80 -j ACCEPT 2>/dev/null || iptables -I INPUT -p tcp --dport 80 -j ACCEPT
iptables -C INPUT -p tcp --dport "$port" -j ACCEPT 2>/dev/null || iptables -I INPUT -p tcp --dport "$port" -j ACCEPT
log "Temporary iptables rules added."
warn "iptables rules may not persist after reboot depending on your Alpine configuration (awall/iptables-save)."
fi
}
stop_port_80_services_if_needed() {
STOPPED_SERVICES=""
if netstat -tlnp | grep -qE '(:80[[:space:]]|:80$)'; then
warn "Something appears to be using port 80."
echo
netstat -tlnp | grep ':80' || true
echo
printf "Temporarily stop nginx/apache2/caddy if running so Certbot can use port 80? [Y/n]: "
read -r yn
yn="${yn:-Y}"
if [ "$yn" = "y" ] || [ "$yn" = "Y" ]; then
for svc in nginx apache2 caddy; do
if rc-service "$svc" status >/dev/null 2>&1; then
rc-service "$svc" stop
STOPPED_SERVICES="$STOPPED_SERVICES $svc"
warn "Stopped $svc temporarily."
fi
done
fi
fi
}
restart_stopped_services() {
if [ -n "$STOPPED_SERVICES" ]; then
info "Restarting services stopped for Certbot..."
for svc in $STOPPED_SERVICES; do
rc-service "$svc" start || warn "Could not restart $svc."
done
fi
}
get_certificate() {
local domain="$1"
local email="$2"
if [ -f "/etc/letsencrypt/live/$domain/fullchain.pem" ] && [ -f "/etc/letsencrypt/live/$domain/privkey.pem" ]; then
log "Existing Let's Encrypt cert found for $domain."
printf "Use existing certificate? [Y/n]: "
read -r yn
yn="${yn:-Y}"
if [ "$yn" = "y" ] || [ "$yn" = "Y" ]; then
return 0
fi
fi
stop_port_80_services_if_needed
info "Requesting Let's Encrypt certificate for $domain..."
if [ -n "$email" ]; then
certbot certonly --standalone \
-d "$domain" \
--non-interactive \
--agree-tos \
--email "$email"
else
certbot certonly --standalone \
-d "$domain" \
--non-interactive \
--agree-tos \
--register-unsafely-without-email
fi
restart_stopped_services
log "Certificate issued."
}
copy_certs() {
local domain="$1"
info "Copying certificate files into /etc/squid/certs..."
mkdir -p /etc/squid/certs
cp "/etc/letsencrypt/live/$domain/fullchain.pem" /etc/squid/certs/proxy-fullchain.pem
cp "/etc/letsencrypt/live/$domain/privkey.pem" /etc/squid/certs/proxy-privkey.pem
chown -R "$SQUID_USER:$SQUID_GROUP" /etc/squid/certs
chmod 644 /etc/squid/certs/proxy-fullchain.pem
chmod 600 /etc/squid/certs/proxy-privkey.pem
log "Certificates copied."
}
create_auth() {
local username="$1"
local password="$2"
info "Creating Squid username/password auth..."
touch /etc/squid/passwd
chown "$SQUID_USER:$SQUID_GROUP" /etc/squid/passwd
chmod 640 /etc/squid/passwd
printf '%s\n' "$password" | htpasswd -m -i /etc/squid/passwd "$username" >/dev/null
chown "$SQUID_USER:$SQUID_GROUP" /etc/squid/passwd
chmod 640 /etc/squid/passwd
log "Proxy user created: $username"
}
test_auth_helper() {
local username="$1"
local password="$2"
info "Testing Squid auth helper directly..."
local result
result="$(printf '%s %s\n' "$username" "$password" | /usr/lib/squid/basic_ncsa_auth /etc/squid/passwd || true)"
if echo "$result" | grep -q '^OK'; then
log "Squid auth helper accepted the username/password."
else
warn "Squid auth helper did not accept the username/password."
echo "Auth helper output:"
echo "$result"
die "Stopping because proxy auth would fail."
fi
}
write_squid_config() {
local domain="$1"
local port="$2"
local syntax="$3"
local https_line
if [ "$syntax" = "new" ]; then
https_line="https_port $port tls-cert=/etc/squid/certs/proxy-fullchain.pem tls-key=/etc/squid/certs/proxy-privkey.pem"
else
https_line="https_port $port cert=/etc/squid/certs/proxy-fullchain.pem key=/etc/squid/certs/proxy-privkey.pem"
fi
info "Writing Squid config using syntax: $syntax"
if [ -f /etc/squid/squid.conf ] && [ ! -f /etc/squid/squid.conf.bak-before-https-proxy ]; then
cp /etc/squid/squid.conf /etc/squid/squid.conf.bak-before-https-proxy
log "Backed up original config to /etc/squid/squid.conf.bak-before-https-proxy"
fi
cat > /etc/squid/squid.conf <<EOF
# ============================================================
# Squid HTTPS/TLS encrypted forward proxy
# Generated by setup-https-squid-proxy.sh
#
# Domain: $domain
# Port: $port
#
# IMPORTANT:
# In FoxyProxy, use host "$domain", not the server IP.
# ============================================================
$https_line
auth_param basic program /usr/lib/squid/basic_ncsa_auth /etc/squid/passwd
auth_param basic realm HTTPS Proxy
auth_param basic credentialsttl 2 hours
acl authenticated proxy_auth REQUIRED
acl SSL_ports port 443
acl Safe_ports port 80
acl Safe_ports port 443
acl Safe_ports port 1025-65535
acl CONNECT method CONNECT
http_access deny !Safe_ports
http_access deny CONNECT !SSL_ports
http_access allow authenticated
http_access deny all
cache deny all
cache_mem 8 MB
maximum_object_size 0 KB
via off
forwarded_for delete
request_header_access X-Forwarded-For deny all
request_header_access Via deny all
request_header_access Cache-Control deny all
access_log /var/log/squid/access.log
cache_log /var/log/squid/cache.log
cache_effective_user $SQUID_USER
cache_effective_group $SQUID_GROUP
visible_hostname $domain
EOF
}
find_working_squid_syntax() {
local domain="$1"
local port="$2"
write_squid_config "$domain" "$port" "new"
if squid -k parse >/tmp/squid-parse.log 2>&1; then
log "Squid config parsed successfully with tls-cert/tls-key syntax."
return 0
fi
warn "tls-cert/tls-key syntax failed. Trying older cert/key syntax..."
cat /tmp/squid-parse.log || true
write_squid_config "$domain" "$port" "old"
if squid -k parse >/tmp/squid-parse.log 2>&1; then
log "Squid config parsed successfully with cert/key syntax."
return 0
fi
cat /tmp/squid-parse.log || true
die "Squid could not parse either HTTPS config style. Your Squid build may not support https_port/OpenSSL."
}
write_renewal_hook() {
local domain="$1"
info "Creating Certbot renewal hook..."
mkdir -p /etc/letsencrypt/renewal-hooks/deploy
cat > /etc/letsencrypt/renewal-hooks/deploy/squid-cert-copy.sh <<EOF
#!/bin/sh
set -e
DOMAIN="$domain"
SRC_CERT="/etc/letsencrypt/live/\$DOMAIN/fullchain.pem"
SRC_KEY="/etc/letsencrypt/live/\$DOMAIN/privkey.pem"
DST_DIR="/etc/squid/certs"
DST_CERT="\$DST_DIR/proxy-fullchain.pem"
DST_KEY="\$DST_DIR/proxy-privkey.pem"
mkdir -p "\$DST_DIR"
cp "\$SRC_CERT" "\$DST_CERT"
cp "\$SRC_KEY" "\$DST_KEY"
chown $SQUID_USER:$SQUID_GROUP "\$DST_CERT" "\$DST_KEY"
chmod 644 "\$DST_CERT"
chmod 600 "\$DST_KEY"
rc-service squid reload || rc-service squid restart
EOF
chmod +x /etc/letsencrypt/renewal-hooks/deploy/squid-cert-copy.sh
log "Renewal hook created at /etc/letsencrypt/renewal-hooks/deploy/squid-cert-copy.sh"
}
restart_squid_cleanly() {
info "Restarting Squid cleanly via OpenRC..."
rc-service squid stop >/dev/null 2>&1 || true
sleep 2
pkill -9 squid >/dev/null 2>&1 || true
sleep 1
rc-service squid start
if rc-service squid status >/dev/null 2>&1; then
rc-update add squid default >/dev/null 2>&1 || true
log "Squid is running and enabled on boot."
else
rc-service squid status || true
tail -100 /var/log/squid/cache.log || true
die "Squid failed to start."
fi
}
test_listener() {
local port="$1"
info "Checking if Squid is listening on port $port..."
if netstat -tlnp | grep -q ":$port "; then
log "Something is listening on port $port:"
netstat -tlnp | grep ":$port " || true
else
warn "Port $port does not appear in netstat output."
fi
}
test_proxy() {
local domain="$1"
local port="$2"
local username="$3"
local password="$4"
info "Testing proxy with curl..."
set +e
local result
result="$(curl -4 -sS --max-time 30 \
--proxy "https://$domain:$port" \
--proxy-user "$username:$password" \
https://chy.li/ip 2>&1)"
local rc=$?
set -e
if [ $rc -eq 0 ]; then
log "Proxy test succeeded. Outgoing IP:"
echo "$result"
else
warn "Curl proxy test failed."
echo "$result"
echo
warn "Check logs:"
echo " tail -f /var/log/squid/cache.log"
echo " tail -f /var/log/squid/access.log"
fi
}
print_summary() {
local domain="$1"
local port="$2"
local username="$3"
echo
echo "============================================================"
echo " HTTPS Squid Proxy Setup Complete"
echo "============================================================"
echo
echo "FoxyProxy settings:"
echo
echo " Proxy Type: HTTPS"
echo " Host: $domain"
echo " Port: $port"
echo " Username: $username"
echo " Password: the password you entered"
echo
echo "IMPORTANT:"
echo " Use the hostname \"$domain\" in FoxyProxy."
echo " Do NOT use the server IP address."
echo
echo "Test command:"
echo
echo " curl -v \\"
echo " --proxy \"https://$domain:$port\" \\"
echo " --proxy-user \"$username:YOUR_PASSWORD\" \\"
echo " https://chy.li/ip"
echo
echo "Useful logs:"
echo
echo " tail -f /var/log/squid/cache.log"
echo " tail -f /var/log/squid/access.log"
echo
echo "============================================================"
}
main() {
require_root
echo
echo "============================================================"
echo " Interactive Squid HTTPS Proxy + Let's Encrypt Installer"
echo " Alpine Linux Edition"
echo "============================================================"
echo
DOMAIN="$(ask "Proxy domain, e.g. proxy.example.com")"
validate_domain "$DOMAIN"
PORT="$(ask "HTTPS proxy port" "8443")"
validate_port "$PORT"
USERNAME="$(ask "Proxy username" "proxyuser")"
if [ -z "$USERNAME" ]; then
die "Username cannot be empty."
fi
PASSWORD="$(ask_password_confirmed)"
EMAIL="$(ask "Email for Let's Encrypt renewal notices, or leave blank" "")"
echo
echo "Summary:"
echo " Domain: $DOMAIN"
echo " Port: $PORT"
echo " Username: $USERNAME"
if [ -n "$EMAIL" ]; then
echo " LE Email: $EMAIL"
else
echo " LE Email: none"
fi
echo
printf "Continue with install? [Y/n]: "
read -r confirm
confirm="${confirm:-Y}"
if [ "$confirm" != "y" ] && [ "$confirm" != "Y" ]; then
die "Cancelled."
fi
install_packages
detect_proxy_user_group
info "Detected Squid user/group: $SQUID_USER:$SQUID_GROUP"
check_squid_openssl
check_dns "$DOMAIN"
open_firewall "$PORT"
get_certificate "$DOMAIN" "$EMAIL"
copy_certs "$DOMAIN"
create_auth "$USERNAME" "$PASSWORD"
test_auth_helper "$USERNAME" "$PASSWORD"
find_working_squid_syntax "$DOMAIN" "$PORT"
write_renewal_hook "$DOMAIN"
restart_squid_cleanly
test_listener "$PORT"
test_proxy "$DOMAIN" "$PORT" "$USERNAME" "$PASSWORD"
print_summary "$DOMAIN" "$PORT" "$USERNAME"
}
main "$@"
| 1 | #!/bin/sh |
| 2 | set -eu |
| 3 | |
| 4 | # ============================================================ |
| 5 | # Interactive Squid HTTPS Proxy Installer with Let's Encrypt |
| 6 | # Alpine Linux Edition (OpenRC) |
| 7 | # |
| 8 | # Creates: |
| 9 | # Firefox/FoxyProxy -> HTTPS proxy -> Squid -> Internet |
| 10 | # |
| 11 | # IMPORTANT: |
| 12 | # In FoxyProxy, use the DOMAIN NAME, not the IP address. |
| 13 | # ============================================================ |
| 14 | |
| 15 | RED="\033[31m" |
| 16 | GREEN="\033[32m" |
| 17 | YELLOW="\033[33m" |
| 18 | BLUE="\033[34m" |
| 19 | RESET="\033[0m" |
| 20 | |
| 21 | log() { printf "${GREEN}[OK]${RESET} %s\n" "$*"; } |
| 22 | info() { printf "${BLUE}[INFO]${RESET} %s\n" "$*"; } |
| 23 | warn() { printf "${YELLOW}[WARN]${RESET} %s\n" "$*"; } |
| 24 | die() { printf "${RED}[ERROR]${RESET} %s\n" "$*" >&2; exit 1; } |
| 25 | |
| 26 | SQUID_USER="squid" |
| 27 | SQUID_GROUP="squid" |
| 28 | STOPPED_SERVICES="" |
| 29 | |
| 30 | require_root() { |
| 31 | if [ "$(id -u)" -ne 0 ]; then |
| 32 | die "Run this script as root." |
| 33 | fi |
| 34 | } |
| 35 | |
| 36 | ask() { |
| 37 | local prompt="$1" |
| 38 | local default="${2:-}" |
| 39 | local value |
| 40 | |
| 41 | # Output the prompt to stderr (>&2) so it displays on the screen |
| 42 | # even when the function's output is being captured by a variable. |
| 43 | if [ -n "$default" ]; then |
| 44 | printf "%s [%s]: " "$prompt" "$default" >&2 |
| 45 | else |
| 46 | printf "%s: " "$prompt" >&2 |
| 47 | fi |
| 48 | read -r value |
| 49 | echo "${value:-$default}" |
| 50 | } |
| 51 | |
| 52 | ask_password_confirmed() { |
| 53 | local pass1 pass2 |
| 54 | |
| 55 | while true; do |
| 56 | printf "Proxy password: " >&2 |
| 57 | stty -echo |
| 58 | read -r pass1 |
| 59 | stty echo |
| 60 | printf '\n' >&2 |
| 61 | |
| 62 | printf "Confirm proxy password: " >&2 |
| 63 | stty -echo |
| 64 | read -r pass2 |
| 65 | stty echo |
| 66 | printf '\n' >&2 |
| 67 | |
| 68 | if [ -z "$pass1" ]; then |
| 69 | warn "Password cannot be empty." >&2 |
| 70 | continue |
| 71 | fi |
| 72 | |
| 73 | if [ "$pass1" != "$pass2" ]; then |
| 74 | warn "Passwords do not match." >&2 |
| 75 | continue |
| 76 | fi |
| 77 | |
| 78 | printf '%s' "$pass1" |
| 79 | return 0 |
| 80 | done |
| 81 | } |
| 82 | |
| 83 | validate_domain() { |
| 84 | local domain="$1" |
| 85 | |
| 86 | if ! echo "$domain" | grep -qE '^[A-Za-z0-9.-]+\.[A-Za-z]{2,}$'; then |
| 87 | die "Invalid domain: $domain" |
| 88 | fi |
| 89 | |
| 90 | if echo "$domain" | grep -qE '^[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+$'; then |
| 91 | die "Do not use an IP address. Use a real hostname, e.g. proxy.example.com" |
| 92 | fi |
| 93 | } |
| 94 | |
| 95 | validate_port() { |
| 96 | local port="$1" |
| 97 | |
| 98 | if ! echo "$port" | grep -qE '^[0-9]+$'; then |
| 99 | die "Invalid port: $port" |
| 100 | fi |
| 101 | |
| 102 | if [ "$port" -lt 1 ] || [ "$port" -gt 65535 ]; then |
| 103 | die "Port must be between 1 and 65535." |
| 104 | fi |
| 105 | } |
| 106 | |
| 107 | detect_proxy_user_group() { |
| 108 | if id squid >/dev/null 2>&1; then |
| 109 | SQUID_USER="squid" |
| 110 | elif id proxy >/dev/null 2>&1; then |
| 111 | SQUID_USER="proxy" |
| 112 | else |
| 113 | SQUID_USER="squid" |
| 114 | fi |
| 115 | |
| 116 | if getent group squid >/dev/null 2>&1; then |
| 117 | SQUID_GROUP="squid" |
| 118 | elif getent group proxy >/dev/null 2>&1; then |
| 119 | SQUID_GROUP="proxy" |
| 120 | else |
| 121 | SQUID_GROUP="$SQUID_USER" |
| 122 | fi |
| 123 | } |
| 124 | |
| 125 | install_packages() { |
| 126 | info "Installing required packages via apk..." |
| 127 | |
| 128 | apk update |
| 129 | apk add squid apache2-utils certbot openssl curl ca-certificates bind-tools iptables |
| 130 | |
| 131 | log "Packages installed." |
| 132 | } |
| 133 | |
| 134 | check_squid_openssl() { |
| 135 | info "Checking Squid OpenSSL support..." |
| 136 | |
| 137 | if squid -v 2>/dev/null | grep -qi openssl; then |
| 138 | log "Squid appears to have OpenSSL support." |
| 139 | else |
| 140 | warn "Could not confirm OpenSSL support from squid -v." |
| 141 | warn "Alpine's default squid should support it, but check logs if https_port fails." |
| 142 | fi |
| 143 | } |
| 144 | |
| 145 | check_dns() { |
| 146 | local domain="$1" |
| 147 | |
| 148 | info "Checking DNS for $domain..." |
| 149 | |
| 150 | local resolved_v4 |
| 151 | local resolved_v6 |
| 152 | |
| 153 | resolved_v4="$(dig +short A "$domain" | tail -n1 || true)" |
| 154 | resolved_v6="$(dig +short AAAA "$domain" | tail -n1 || true)" |
| 155 | |
| 156 | if [ -z "$resolved_v4" ] && [ -z "$resolved_v6" ]; then |
| 157 | warn "No A or AAAA record found for $domain." |
| 158 | warn "Let's Encrypt will fail unless this hostname points to this server." |
| 159 | printf "Continue anyway? [y/N]: " |
| 160 | read -r yn |
| 161 | if [ "$yn" != "y" ] && [ "$yn" != "Y" ]; then |
| 162 | die "Stopped." |
| 163 | fi |
| 164 | else |
| 165 | if [ -n "$resolved_v4" ]; then |
| 166 | log "$domain A record: $resolved_v4" |
| 167 | fi |
| 168 | |
| 169 | if [ -n "$resolved_v6" ]; then |
| 170 | log "$domain AAAA record: $resolved_v6" |
| 171 | else |
| 172 | warn "$domain has no AAAA record. That is fine if you only want IPv4." |
| 173 | fi |
| 174 | fi |
| 175 | |
| 176 | return 0 |
| 177 | } |
| 178 | |
| 179 | open_firewall() { |
| 180 | local port="$1" |
| 181 | |
| 182 | info "Attempting to open firewall ports 80 and $port..." |
| 183 | |
| 184 | if command -v iptables >/dev/null 2>&1; then |
| 185 | iptables -C INPUT -p tcp --dport 80 -j ACCEPT 2>/dev/null || iptables -I INPUT -p tcp --dport 80 -j ACCEPT |
| 186 | iptables -C INPUT -p tcp --dport "$port" -j ACCEPT 2>/dev/null || iptables -I INPUT -p tcp --dport "$port" -j ACCEPT |
| 187 | log "Temporary iptables rules added." |
| 188 | warn "iptables rules may not persist after reboot depending on your Alpine configuration (awall/iptables-save)." |
| 189 | fi |
| 190 | } |
| 191 | |
| 192 | stop_port_80_services_if_needed() { |
| 193 | STOPPED_SERVICES="" |
| 194 | |
| 195 | if netstat -tlnp | grep -qE '(:80[[:space:]]|:80$)'; then |
| 196 | warn "Something appears to be using port 80." |
| 197 | echo |
| 198 | netstat -tlnp | grep ':80' || true |
| 199 | echo |
| 200 | |
| 201 | printf "Temporarily stop nginx/apache2/caddy if running so Certbot can use port 80? [Y/n]: " |
| 202 | read -r yn |
| 203 | yn="${yn:-Y}" |
| 204 | |
| 205 | if [ "$yn" = "y" ] || [ "$yn" = "Y" ]; then |
| 206 | for svc in nginx apache2 caddy; do |
| 207 | if rc-service "$svc" status >/dev/null 2>&1; then |
| 208 | rc-service "$svc" stop |
| 209 | STOPPED_SERVICES="$STOPPED_SERVICES $svc" |
| 210 | warn "Stopped $svc temporarily." |
| 211 | fi |
| 212 | done |
| 213 | fi |
| 214 | fi |
| 215 | } |
| 216 | |
| 217 | restart_stopped_services() { |
| 218 | if [ -n "$STOPPED_SERVICES" ]; then |
| 219 | info "Restarting services stopped for Certbot..." |
| 220 | |
| 221 | for svc in $STOPPED_SERVICES; do |
| 222 | rc-service "$svc" start || warn "Could not restart $svc." |
| 223 | done |
| 224 | fi |
| 225 | } |
| 226 | |
| 227 | get_certificate() { |
| 228 | local domain="$1" |
| 229 | local email="$2" |
| 230 | |
| 231 | if [ -f "/etc/letsencrypt/live/$domain/fullchain.pem" ] && [ -f "/etc/letsencrypt/live/$domain/privkey.pem" ]; then |
| 232 | log "Existing Let's Encrypt cert found for $domain." |
| 233 | printf "Use existing certificate? [Y/n]: " |
| 234 | read -r yn |
| 235 | yn="${yn:-Y}" |
| 236 | |
| 237 | if [ "$yn" = "y" ] || [ "$yn" = "Y" ]; then |
| 238 | return 0 |
| 239 | fi |
| 240 | fi |
| 241 | |
| 242 | stop_port_80_services_if_needed |
| 243 | |
| 244 | info "Requesting Let's Encrypt certificate for $domain..." |
| 245 | |
| 246 | if [ -n "$email" ]; then |
| 247 | certbot certonly --standalone \ |
| 248 | -d "$domain" \ |
| 249 | --non-interactive \ |
| 250 | --agree-tos \ |
| 251 | --email "$email" |
| 252 | else |
| 253 | certbot certonly --standalone \ |
| 254 | -d "$domain" \ |
| 255 | --non-interactive \ |
| 256 | --agree-tos \ |
| 257 | --register-unsafely-without-email |
| 258 | fi |
| 259 | |
| 260 | restart_stopped_services |
| 261 | |
| 262 | log "Certificate issued." |
| 263 | } |
| 264 | |
| 265 | copy_certs() { |
| 266 | local domain="$1" |
| 267 | |
| 268 | info "Copying certificate files into /etc/squid/certs..." |
| 269 | |
| 270 | mkdir -p /etc/squid/certs |
| 271 | |
| 272 | cp "/etc/letsencrypt/live/$domain/fullchain.pem" /etc/squid/certs/proxy-fullchain.pem |
| 273 | cp "/etc/letsencrypt/live/$domain/privkey.pem" /etc/squid/certs/proxy-privkey.pem |
| 274 | |
| 275 | chown -R "$SQUID_USER:$SQUID_GROUP" /etc/squid/certs |
| 276 | chmod 644 /etc/squid/certs/proxy-fullchain.pem |
| 277 | chmod 600 /etc/squid/certs/proxy-privkey.pem |
| 278 | |
| 279 | log "Certificates copied." |
| 280 | } |
| 281 | |
| 282 | create_auth() { |
| 283 | local username="$1" |
| 284 | local password="$2" |
| 285 | |
| 286 | info "Creating Squid username/password auth..." |
| 287 | |
| 288 | touch /etc/squid/passwd |
| 289 | chown "$SQUID_USER:$SQUID_GROUP" /etc/squid/passwd |
| 290 | chmod 640 /etc/squid/passwd |
| 291 | |
| 292 | printf '%s\n' "$password" | htpasswd -m -i /etc/squid/passwd "$username" >/dev/null |
| 293 | |
| 294 | chown "$SQUID_USER:$SQUID_GROUP" /etc/squid/passwd |
| 295 | chmod 640 /etc/squid/passwd |
| 296 | |
| 297 | log "Proxy user created: $username" |
| 298 | } |
| 299 | |
| 300 | test_auth_helper() { |
| 301 | local username="$1" |
| 302 | local password="$2" |
| 303 | |
| 304 | info "Testing Squid auth helper directly..." |
| 305 | |
| 306 | local result |
| 307 | result="$(printf '%s %s\n' "$username" "$password" | /usr/lib/squid/basic_ncsa_auth /etc/squid/passwd || true)" |
| 308 | |
| 309 | if echo "$result" | grep -q '^OK'; then |
| 310 | log "Squid auth helper accepted the username/password." |
| 311 | else |
| 312 | warn "Squid auth helper did not accept the username/password." |
| 313 | echo "Auth helper output:" |
| 314 | echo "$result" |
| 315 | die "Stopping because proxy auth would fail." |
| 316 | fi |
| 317 | } |
| 318 | |
| 319 | write_squid_config() { |
| 320 | local domain="$1" |
| 321 | local port="$2" |
| 322 | local syntax="$3" |
| 323 | |
| 324 | local https_line |
| 325 | |
| 326 | if [ "$syntax" = "new" ]; then |
| 327 | https_line="https_port $port tls-cert=/etc/squid/certs/proxy-fullchain.pem tls-key=/etc/squid/certs/proxy-privkey.pem" |
| 328 | else |
| 329 | https_line="https_port $port cert=/etc/squid/certs/proxy-fullchain.pem key=/etc/squid/certs/proxy-privkey.pem" |
| 330 | fi |
| 331 | |
| 332 | info "Writing Squid config using syntax: $syntax" |
| 333 | |
| 334 | if [ -f /etc/squid/squid.conf ] && [ ! -f /etc/squid/squid.conf.bak-before-https-proxy ]; then |
| 335 | cp /etc/squid/squid.conf /etc/squid/squid.conf.bak-before-https-proxy |
| 336 | log "Backed up original config to /etc/squid/squid.conf.bak-before-https-proxy" |
| 337 | fi |
| 338 | |
| 339 | cat > /etc/squid/squid.conf <<EOF |
| 340 | # ============================================================ |
| 341 | # Squid HTTPS/TLS encrypted forward proxy |
| 342 | # Generated by setup-https-squid-proxy.sh |
| 343 | # |
| 344 | # Domain: $domain |
| 345 | # Port: $port |
| 346 | # |
| 347 | # IMPORTANT: |
| 348 | # In FoxyProxy, use host "$domain", not the server IP. |
| 349 | # ============================================================ |
| 350 | |
| 351 | $https_line |
| 352 | |
| 353 | auth_param basic program /usr/lib/squid/basic_ncsa_auth /etc/squid/passwd |
| 354 | auth_param basic realm HTTPS Proxy |
| 355 | auth_param basic credentialsttl 2 hours |
| 356 | |
| 357 | acl authenticated proxy_auth REQUIRED |
| 358 | |
| 359 | acl SSL_ports port 443 |
| 360 | acl Safe_ports port 80 |
| 361 | acl Safe_ports port 443 |
| 362 | acl Safe_ports port 1025-65535 |
| 363 | acl CONNECT method CONNECT |
| 364 | |
| 365 | http_access deny !Safe_ports |
| 366 | http_access deny CONNECT !SSL_ports |
| 367 | http_access allow authenticated |
| 368 | http_access deny all |
| 369 | |
| 370 | cache deny all |
| 371 | cache_mem 8 MB |
| 372 | maximum_object_size 0 KB |
| 373 | |
| 374 | via off |
| 375 | forwarded_for delete |
| 376 | request_header_access X-Forwarded-For deny all |
| 377 | request_header_access Via deny all |
| 378 | request_header_access Cache-Control deny all |
| 379 | |
| 380 | access_log /var/log/squid/access.log |
| 381 | cache_log /var/log/squid/cache.log |
| 382 | |
| 383 | cache_effective_user $SQUID_USER |
| 384 | cache_effective_group $SQUID_GROUP |
| 385 | |
| 386 | visible_hostname $domain |
| 387 | EOF |
| 388 | } |
| 389 | |
| 390 | find_working_squid_syntax() { |
| 391 | local domain="$1" |
| 392 | local port="$2" |
| 393 | |
| 394 | write_squid_config "$domain" "$port" "new" |
| 395 | |
| 396 | if squid -k parse >/tmp/squid-parse.log 2>&1; then |
| 397 | log "Squid config parsed successfully with tls-cert/tls-key syntax." |
| 398 | return 0 |
| 399 | fi |
| 400 | |
| 401 | warn "tls-cert/tls-key syntax failed. Trying older cert/key syntax..." |
| 402 | cat /tmp/squid-parse.log || true |
| 403 | |
| 404 | write_squid_config "$domain" "$port" "old" |
| 405 | |
| 406 | if squid -k parse >/tmp/squid-parse.log 2>&1; then |
| 407 | log "Squid config parsed successfully with cert/key syntax." |
| 408 | return 0 |
| 409 | fi |
| 410 | |
| 411 | cat /tmp/squid-parse.log || true |
| 412 | die "Squid could not parse either HTTPS config style. Your Squid build may not support https_port/OpenSSL." |
| 413 | } |
| 414 | |
| 415 | write_renewal_hook() { |
| 416 | local domain="$1" |
| 417 | |
| 418 | info "Creating Certbot renewal hook..." |
| 419 | |
| 420 | mkdir -p /etc/letsencrypt/renewal-hooks/deploy |
| 421 | |
| 422 | cat > /etc/letsencrypt/renewal-hooks/deploy/squid-cert-copy.sh <<EOF |
| 423 | #!/bin/sh |
| 424 | set -e |
| 425 | |
| 426 | DOMAIN="$domain" |
| 427 | |
| 428 | SRC_CERT="/etc/letsencrypt/live/\$DOMAIN/fullchain.pem" |
| 429 | SRC_KEY="/etc/letsencrypt/live/\$DOMAIN/privkey.pem" |
| 430 | |
| 431 | DST_DIR="/etc/squid/certs" |
| 432 | DST_CERT="\$DST_DIR/proxy-fullchain.pem" |
| 433 | DST_KEY="\$DST_DIR/proxy-privkey.pem" |
| 434 | |
| 435 | mkdir -p "\$DST_DIR" |
| 436 | |
| 437 | cp "\$SRC_CERT" "\$DST_CERT" |
| 438 | cp "\$SRC_KEY" "\$DST_KEY" |
| 439 | |
| 440 | chown $SQUID_USER:$SQUID_GROUP "\$DST_CERT" "\$DST_KEY" |
| 441 | chmod 644 "\$DST_CERT" |
| 442 | chmod 600 "\$DST_KEY" |
| 443 | |
| 444 | rc-service squid reload || rc-service squid restart |
| 445 | EOF |
| 446 | |
| 447 | chmod +x /etc/letsencrypt/renewal-hooks/deploy/squid-cert-copy.sh |
| 448 | |
| 449 | log "Renewal hook created at /etc/letsencrypt/renewal-hooks/deploy/squid-cert-copy.sh" |
| 450 | } |
| 451 | |
| 452 | restart_squid_cleanly() { |
| 453 | info "Restarting Squid cleanly via OpenRC..." |
| 454 | |
| 455 | rc-service squid stop >/dev/null 2>&1 || true |
| 456 | sleep 2 |
| 457 | |
| 458 | pkill -9 squid >/dev/null 2>&1 || true |
| 459 | sleep 1 |
| 460 | |
| 461 | rc-service squid start |
| 462 | |
| 463 | if rc-service squid status >/dev/null 2>&1; then |
| 464 | rc-update add squid default >/dev/null 2>&1 || true |
| 465 | log "Squid is running and enabled on boot." |
| 466 | else |
| 467 | rc-service squid status || true |
| 468 | tail -100 /var/log/squid/cache.log || true |
| 469 | die "Squid failed to start." |
| 470 | fi |
| 471 | } |
| 472 | |
| 473 | test_listener() { |
| 474 | local port="$1" |
| 475 | |
| 476 | info "Checking if Squid is listening on port $port..." |
| 477 | |
| 478 | if netstat -tlnp | grep -q ":$port "; then |
| 479 | log "Something is listening on port $port:" |
| 480 | netstat -tlnp | grep ":$port " || true |
| 481 | else |
| 482 | warn "Port $port does not appear in netstat output." |
| 483 | fi |
| 484 | } |
| 485 | |
| 486 | test_proxy() { |
| 487 | local domain="$1" |
| 488 | local port="$2" |
| 489 | local username="$3" |
| 490 | local password="$4" |
| 491 | |
| 492 | info "Testing proxy with curl..." |
| 493 | |
| 494 | set +e |
| 495 | local result |
| 496 | result="$(curl -4 -sS --max-time 30 \ |
| 497 | --proxy "https://$domain:$port" \ |
| 498 | --proxy-user "$username:$password" \ |
| 499 | https://chy.li/ip 2>&1)" |
| 500 | local rc=$? |
| 501 | set -e |
| 502 | |
| 503 | if [ $rc -eq 0 ]; then |
| 504 | log "Proxy test succeeded. Outgoing IP:" |
| 505 | echo "$result" |
| 506 | else |
| 507 | warn "Curl proxy test failed." |
| 508 | echo "$result" |
| 509 | echo |
| 510 | warn "Check logs:" |
| 511 | echo " tail -f /var/log/squid/cache.log" |
| 512 | echo " tail -f /var/log/squid/access.log" |
| 513 | fi |
| 514 | } |
| 515 | |
| 516 | print_summary() { |
| 517 | local domain="$1" |
| 518 | local port="$2" |
| 519 | local username="$3" |
| 520 | |
| 521 | echo |
| 522 | echo "============================================================" |
| 523 | echo " HTTPS Squid Proxy Setup Complete" |
| 524 | echo "============================================================" |
| 525 | echo |
| 526 | echo "FoxyProxy settings:" |
| 527 | echo |
| 528 | echo " Proxy Type: HTTPS" |
| 529 | echo " Host: $domain" |
| 530 | echo " Port: $port" |
| 531 | echo " Username: $username" |
| 532 | echo " Password: the password you entered" |
| 533 | echo |
| 534 | echo "IMPORTANT:" |
| 535 | echo " Use the hostname \"$domain\" in FoxyProxy." |
| 536 | echo " Do NOT use the server IP address." |
| 537 | echo |
| 538 | echo "Test command:" |
| 539 | echo |
| 540 | echo " curl -v \\" |
| 541 | echo " --proxy \"https://$domain:$port\" \\" |
| 542 | echo " --proxy-user \"$username:YOUR_PASSWORD\" \\" |
| 543 | echo " https://chy.li/ip" |
| 544 | echo |
| 545 | echo "Useful logs:" |
| 546 | echo |
| 547 | echo " tail -f /var/log/squid/cache.log" |
| 548 | echo " tail -f /var/log/squid/access.log" |
| 549 | echo |
| 550 | echo "============================================================" |
| 551 | } |
| 552 | |
| 553 | main() { |
| 554 | require_root |
| 555 | |
| 556 | echo |
| 557 | echo "============================================================" |
| 558 | echo " Interactive Squid HTTPS Proxy + Let's Encrypt Installer" |
| 559 | echo " Alpine Linux Edition" |
| 560 | echo "============================================================" |
| 561 | echo |
| 562 | |
| 563 | DOMAIN="$(ask "Proxy domain, e.g. proxy.example.com")" |
| 564 | validate_domain "$DOMAIN" |
| 565 | |
| 566 | PORT="$(ask "HTTPS proxy port" "8443")" |
| 567 | validate_port "$PORT" |
| 568 | |
| 569 | USERNAME="$(ask "Proxy username" "proxyuser")" |
| 570 | if [ -z "$USERNAME" ]; then |
| 571 | die "Username cannot be empty." |
| 572 | fi |
| 573 | |
| 574 | PASSWORD="$(ask_password_confirmed)" |
| 575 | |
| 576 | EMAIL="$(ask "Email for Let's Encrypt renewal notices, or leave blank" "")" |
| 577 | |
| 578 | echo |
| 579 | echo "Summary:" |
| 580 | echo " Domain: $DOMAIN" |
| 581 | echo " Port: $PORT" |
| 582 | echo " Username: $USERNAME" |
| 583 | if [ -n "$EMAIL" ]; then |
| 584 | echo " LE Email: $EMAIL" |
| 585 | else |
| 586 | echo " LE Email: none" |
| 587 | fi |
| 588 | echo |
| 589 | |
| 590 | printf "Continue with install? [Y/n]: " |
| 591 | read -r confirm |
| 592 | confirm="${confirm:-Y}" |
| 593 | if [ "$confirm" != "y" ] && [ "$confirm" != "Y" ]; then |
| 594 | die "Cancelled." |
| 595 | fi |
| 596 | |
| 597 | install_packages |
| 598 | detect_proxy_user_group |
| 599 | |
| 600 | info "Detected Squid user/group: $SQUID_USER:$SQUID_GROUP" |
| 601 | |
| 602 | check_squid_openssl |
| 603 | check_dns "$DOMAIN" |
| 604 | open_firewall "$PORT" |
| 605 | get_certificate "$DOMAIN" "$EMAIL" |
| 606 | copy_certs "$DOMAIN" |
| 607 | create_auth "$USERNAME" "$PASSWORD" |
| 608 | test_auth_helper "$USERNAME" "$PASSWORD" |
| 609 | find_working_squid_syntax "$DOMAIN" "$PORT" |
| 610 | write_renewal_hook "$DOMAIN" |
| 611 | restart_squid_cleanly |
| 612 | test_listener "$PORT" |
| 613 | test_proxy "$DOMAIN" "$PORT" "$USERNAME" "$PASSWORD" |
| 614 | print_summary "$DOMAIN" "$PORT" "$USERNAME" |
| 615 | } |
| 616 | |
| 617 | main "$@" |